Day 003 — 制御構文(if/for/switch)

2026-07-31 🟢 初心者 / Phase 1 実装 制御構文(if/for/switch)

📚 背景知識(読んでから問題へ)

Goにはwhiledo-whileもキーワードとして存在しません。ループを表現するキーワードはforただ1つです。これは手抜きではなく、「言語仕様を小さく保つ」というGoの一貫した設計思想の表れです。forは書き方によって以下の3パターンをすべて表現できます。

for i := 0; i < 10; i++ { }  // 通常のfor(初期化; 条件; 後処理)
for cond { }                  // while相当(条件式のみ)
for { }                        // 無限ループ(条件式を省略 = for true と同義)

switchはfallthroughしないのがデフォルト

一方switchはC言語やJava、JavaScriptとは挙動が異なります。他言語のswitchはデフォルトで「fallthrough(次のcaseに処理が継続する)」するため、各caseの末尾にbreakを書き忘れると意図しないバグになります。Goのswitch各caseの実行後に自動的にbreakするのがデフォルトで、次のcaseに処理を継続させたい場合は明示的にfallthroughキーワードを書く必要があります。

条件式を省略したswitch(switch true 相当)

Goのswitchは条件式を省略できます。省略すると各caseは真偽値の式として評価され、上から順に最初にtrueになったcaseが実行されます。これはif-else if-elseチェーンと全く同じ意味を持ちながら、より読みやすく書ける代替表現です。

switch {
case n < 0:
    fmt.Println("負の数")
case n == 0:
    fmt.Println("ゼロ")
default:
    fmt.Println("正の数")
}

キーワードの数を減らしつつ表現力を落とさない、という設計判断がGoの制御構文全体に一貫して見られます。

📝 問題

FizzBuzzルールに従って整数を文字列に分類する関数を実装し、それを3種類の異なるforループパターンから利用するプログラムを書いてください。

要件:

  1. Classify(n int) string 関数を実装する。ルールは以下の通り。
    • nが15で割り切れる → "FizzBuzz"
    • nが3で割り切れる → "Fizz"
    • nが5で割り切れる → "Buzz"
    • それ以外 → nを文字列化したもの(例: "7"
    • 条件式を省略したswitch文(switch { case ... }の形)を使って実装すること
  2. main関数内で、1から20までの範囲に対して以下の3パターンをすべて実装し、それぞれClassifyの結果を出力する。
    • パターンA: 通常のfor(初期化; 条件; 後処理)
    • パターンB: while相当のfor(条件式のみ)
    • パターンC: 無限for + breakを使い、「最初に"FizzBuzz"になる数」を見つけたら結果を表示してループを抜ける

go run main.go でそのまま実行できる、完全な1つのプログラムとして提出してください。

🔍 ヒント(段階的開示)

ヒント1 — 方向性
  • Goにはwhileキーワードがありません。「while(条件)と書きたい」と思ったら、forの初期化部分と後処理部分を省略し、条件式だけを残した形(for 条件 { })にすればそのままwhileとして動きます
  • switchはcase内で自動的に処理が止まる(他言語のようにfallthroughしない)ことを思い出してください
ヒント2 — アプローチ
  • FizzBuzzでは「15の倍数」は「3の倍数」でも「5の倍数」でもあります。switchの各case上から順に評価され、最初にtrueになったものだけが実行される(他のcaseは評価すらされない)ため、case n%15 == 0:を一番上に書く必要があります
  • 無限ループ+breakのパターンでは、ループの中でClassify(i)を呼び、その結果が"FizzBuzz"かどうかをifで判定してからbreakします
ヒント3 — コード骨格
package main

import "fmt"

func Classify(n int) string {
	switch {
	case n%15 == 0:
		return "FizzBuzz"
	case /* ??? */:
		return "Fizz"
	case /* ??? */:
		return "Buzz"
	default:
		return /* ??? */
	}
}

func main() {
	const max = 20

	// パターンA: 通常のfor
	for i := 1; i <= max; i++ {
		// ここでClassifyを呼んで出力
	}

	// パターンB: while相当のfor
	counter := 1
	for /* 条件だけ */ {
		// ここでClassifyを呼んで出力し、counterを進める
	}

	// パターンC: 無限for + break
	i := 1
	for {
		// Classify(i) が "FizzBuzz" ならbreakする
	}
}

残りのcase条件・defaultの返り値・各forブロックの中身を埋めてください。

模範解答

package main

import "fmt"

// Classify はFizzBuzzルールに従ってnを分類する。
// switchの条件式を省略した形(switch true 相当)はif-elseチェーンの代替になる。
func Classify(n int) string {
	switch {
	case n%15 == 0:
		return "FizzBuzz"
	case n%3 == 0:
		return "Fizz"
	case n%5 == 0:
		return "Buzz"
	default:
		return fmt.Sprintf("%d", n)
	}
}

func main() {
	const max = 20

	// パターンA: 通常のfor(初期化; 条件; 後処理)
	fmt.Println("--- パターンA: 通常のfor ---")
	for i := 1; i <= max; i++ {
		fmt.Println(Classify(i))
	}

	// パターンB: while相当のfor(条件式のみ)
	fmt.Println("--- パターンB: while相当のfor ---")
	counter := 1
	for counter <= max {
		fmt.Println(Classify(counter))
		counter++
	}

	// パターンC: 無限for + break
	fmt.Println("--- パターンC: 無限for + break ---")
	i := 1
	for {
		if Classify(i) == "FizzBuzz" {
			fmt.Printf("最初のFizzBuzzは %d\n", i)
			break
		}
		i++
	}
}
▶ 実行結果を見る(go run main.go)
--- パターンA: 通常のfor ---
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz
--- パターンB: while相当のfor ---
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz
--- パターンC: 無限for + break ---
最初のFizzBuzzは 15

🪜 Step-by-Step 解説

1
switch trueパターンでFizzBuzzを実装する
switch {
case n%15 == 0:
	return "FizzBuzz"
case n%3 == 0:
	return "Fizz"
case n%5 == 0:
	return "Buzz"
default:
	return fmt.Sprintf("%d", n)
}
switchの直後に条件式を書かないと、各caseは独立した真偽値の式として扱われます。Goのswitchは上から順に評価し、最初にtrueになったcaseが実行された時点で(他言語と違い)自動的にそこで処理が終わります。だからこそ「15の倍数」を判定するcaseを一番上に置く必要があります。もしn%3==0を先に書いてしまうと、15は3の倍数でもあるため"Fizz"が返ってしまい、n%15==0のcaseには一生到達しません。
2
通常のfor(C言語スタイル)
for i := 1; i <= max; i++ {
	fmt.Println(Classify(i))
}
for 初期化; 条件; 後処理 { }という最も見慣れた形です。カウンタ変数iの宣言・終了条件・毎回の更新処理が1行にまとまっているため、「決まった回数だけ回す」ループの意図が一目でわかります。
3
while相当のfor(条件式のみ)
counter := 1
for counter <= max {
	fmt.Println(Classify(counter))
	counter++
}
初期化部分と後処理部分を省略し、条件式だけを残すと、他言語のwhile (counter <= max) { ... }と全く同じ意味になります。Goはこのために新しいキーワードを用意せず、forの省略記法だけでカバーしています。
4
無限for + break
i := 1
for {
	if Classify(i) == "FizzBuzz" {
		fmt.Printf("最初のFizzBuzzは %d\n", i)
		break
	}
	i++
}
条件式そのものを省略するとfor true { }と同じ、つまり無限ループになります。ループを終了させる条件が「何回目で終わるか事前にわからない」(ここでは「最初にFizzBuzzになる数を見つけるまで」)ようなケースでは、無限ループの中にifbreakを組み合わせるのがGoの定石パターンです。

💡 設計思想・なぜこう書くのか

📌
キーワードの数を最小限にする: whiledo-whileforという3つの別々のキーワードを用意する代わりに、for1つの省略記法だけで同じ表現力をカバーすることで、文法規則そのものをシンプルに保っています。学習コストが下がるだけでなく、「このループはfor文なのかwhile文なのか」という無駄な使い分けの判断も発生しません。
📌
安全な既定値を選ぶ: C言語では「break書き忘れ」がベテランでもやりがちな古典的バグであり、多くの静的解析ツールがこれを警告する定番項目になっています。Goは「うっかりミスが起きやすい挙動をデフォルトにしない」という安全側の設計を選び、明示的にfallthroughと書いた場合だけ次のcaseに継続するようにしました。この判断は、Goの他の仕様(エラーを戻り値で明示させる、ゼロ値を安全な状態にする等)にも一貫して見られます。

🌐 他言語との比較

観点GoJava / CJavaScriptPython
ループ用キーワードforのみ(3パターンを省略記法で表現)for / while / do-whileの3種for / while / do-whileの3種for / whileの2種(do-whileはない)
switchのfallthroughデフォルトOFF(fallthroughで明示指定)デフォルトON(break忘れが定番バグ)デフォルトON(break忘れが定番バグ)switch文自体がなかった(3.10でmatchが追加、fallthroughなし)
条件式なしswitchswitch { case 式: }でif-elseチェーンの代替になる非対応(switchは値の一致判定のみ)非対応(switchは値の一致判定のみ)matchは主にパターンマッチ用途

C/Java/JavaScriptに慣れているほど「Goにはwhileがない」ことに違和感を覚えがちですが、for 条件 { }が完全にwhileの代替になっているため表現力の損失はありません。逆にswitchのfallthrough挙動はGoの方が直感的(「上から順に1つだけ実行される」という素直な理解でよい)で、他言語からの移行者が最初に得をするポイントの1つです。

🏆 実務での使いどころ

  • リトライループ: 外部APIの呼び出しなどで「成功するかリトライ上限に達するまで」ループする処理は、無限for + breakの典型的な使いどころです
  • ワーカーの受信ループ: goroutineがchannelからメッセージを受け取り続ける処理(for { select { ... } })は並行処理コードで非常に頻出するパターンで、無限forの実務での代表例です
  • ステータス分岐処理: HTTPステータスコードの分類(2xx/4xx/5xxの判定)やバリデーションルールの分岐など、複数条件を上から順に評価したい場面ではswitch trueパターンがif-elseチェーンより見通しよく書けます
  • ポーリング処理: 一定間隔でリソースの状態を確認し続けるような処理は、while相当のfor(条件付きループ)で自然に表現できます

⚠️ よくある誤解・ミス

誤解・ミスなぜ起こるか正しい理解
Goにはwhileがないので不便・代替手段がない他言語のwhileキーワードに慣れているためfor 条件 { }whileと完全に同じ意味。キーワードが減っただけで表現力は失われていない
Goのswitchも他言語のようにcase間でfallthroughすると思い込むC/Java/JavaScript由来の直感を引きずるGoのswitchは各case実行後に自動でbreakする。次のcaseに継続させたい場合のみ明示的にfallthroughと書く
for {}は無限ループだから危険・避けるべきと考える「無限」という言葉自体への警戒心breakreturnと組み合わせて終了条件を明示すれば安全な定石パターン。サーバーの受信ループなど実務で頻出する
switch trueパターンで、複数のcaseに同時に当てはまる値がある場合の優先順位を意識しない各caseが独立した条件だと思い込み、上から評価される順序を意識しない上から順に評価され、最初にtrueになったcaseだけが実行される。範囲が重なる条件(15の倍数は3の倍数でもある)は狭い条件・優先度の高い条件を上に書く必要がある

🚀 次のステップ

  • 発展: switch文でfallthroughキーワードを明示的に使い、意図的に次のcaseへ処理を継続させる例を書いてみましょう。また複数の値を1つのcaseにまとめるcase 3, 6, 9:のような書き方も試してみましょう
  • 次回予告: Day 004 — 配列とスライス①(作成・アクセス・append)(配列とスライスの違い・appendの挙動)

🎯 自己評価

自分の回答

気づき・メモ