📚 背景知識(読んでから問題へ)
Goにはwhileもdo-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ループパターンから利用するプログラムを書いてください。
要件:
Classify(n int) string関数を実装する。ルールは以下の通り。nが15で割り切れる →"FizzBuzz"nが3で割り切れる →"Fizz"nが5で割り切れる →"Buzz"- それ以外 →
nを文字列化したもの(例:"7") - 条件式を省略したswitch文(
switch { case ... }の形)を使って実装すること
main関数内で、1から20までの範囲に対して以下の3パターンをすべて実装し、それぞれClassifyの結果を出力する。- パターンA: 通常の
for(初期化; 条件; 後処理) - パターンB: while相当の
for(条件式のみ) - パターンC: 無限
for+breakを使い、「最初に"FizzBuzz"になる数」を見つけたら結果を表示してループを抜ける
- パターンA: 通常の
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 解説
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には一生到達しません。
for i := 1; i <= max; i++ {
fmt.Println(Classify(i))
}
for 初期化; 条件; 後処理 { }という最も見慣れた形です。カウンタ変数iの宣言・終了条件・毎回の更新処理が1行にまとまっているため、「決まった回数だけ回す」ループの意図が一目でわかります。
counter := 1
for counter <= max {
fmt.Println(Classify(counter))
counter++
}
初期化部分と後処理部分を省略し、条件式だけを残すと、他言語のwhile (counter <= max) { ... }と全く同じ意味になります。Goはこのために新しいキーワードを用意せず、forの省略記法だけでカバーしています。
i := 1
for {
if Classify(i) == "FizzBuzz" {
fmt.Printf("最初のFizzBuzzは %d\n", i)
break
}
i++
}
条件式そのものを省略するとfor true { }と同じ、つまり無限ループになります。ループを終了させる条件が「何回目で終わるか事前にわからない」(ここでは「最初にFizzBuzzになる数を見つけるまで」)ようなケースでは、無限ループの中にifとbreakを組み合わせるのがGoの定石パターンです。
💡 設計思想・なぜこう書くのか
while・do-while・forという3つの別々のキーワードを用意する代わりに、for1つの省略記法だけで同じ表現力をカバーすることで、文法規則そのものをシンプルに保っています。学習コストが下がるだけでなく、「このループはfor文なのかwhile文なのか」という無駄な使い分けの判断も発生しません。fallthroughと書いた場合だけ次のcaseに継続するようにしました。この判断は、Goの他の仕様(エラーを戻り値で明示させる、ゼロ値を安全な状態にする等)にも一貫して見られます。🌐 他言語との比較
| 観点 | Go | Java / C | JavaScript | Python |
|---|---|---|---|---|
| ループ用キーワード | 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なし) |
| 条件式なしswitch | switch { 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 {}は無限ループだから危険・避けるべきと考える | 「無限」という言葉自体への警戒心 | breakやreturnと組み合わせて終了条件を明示すれば安全な定石パターン。サーバーの受信ループなど実務で頻出する |
| switch trueパターンで、複数のcaseに同時に当てはまる値がある場合の優先順位を意識しない | 各caseが独立した条件だと思い込み、上から評価される順序を意識しない | 上から順に評価され、最初にtrueになったcaseだけが実行される。範囲が重なる条件(15の倍数は3の倍数でもある)は狭い条件・優先度の高い条件を上に書く必要がある |
🚀 次のステップ
- 発展:
switch文でfallthroughキーワードを明示的に使い、意図的に次のcaseへ処理を継続させる例を書いてみましょう。また複数の値を1つのcaseにまとめるcase 3, 6, 9:のような書き方も試してみましょう - 次回予告: Day 004 — 配列とスライス①(作成・アクセス・append)(配列とスライスの違い・appendの挙動)