Go: Goの制御フロー:4つの主要な制御フロー文――if、switch、for、およびdefer

制御フローはプログラムに「思考」をもたらします。Go言語は、たった4つのシンプルなキーワードであらゆる制御フローのシナリオを網羅しており、whiledo-whileループのような冗長性を排除しています。

Goの制御フローの設計哲学は、「Less is more」です。ifswitchfordeferの4つのキーワードに、breakcontinuelabelを組み合わせれば、ビジネスロジックの99%を処理するのに十分です。このレッスンでは、Goの制御フローに関するすべての核心的な概念を習得します。

1. 学習内容



2. オンライン教育プラットフォームに関する実話

(1) 課題:試験の採点に500行ものC++コードを使用しているにもかかわらず、バグが後を絶たない

チャーリーは、あるオンライン教育プラットフォームのバックエンドエンジニアです。彼はつい先日、自動採点システムのコードを引き継いだばかりです:

「首相によると、現在、1,000枚の試験答案を処理するのに8時間かかっており、バグが発生するたびに、『範囲外』エラーか、C言語のif文から{}が抜け落ちているかのどちらかで、その結果、その学年の成績全体がめちゃくちゃになってしまうそうだ……」

彼は前任者が残していったC++のコードを開いて、目を通した:

CPP
// C++ Style: Verbose + Prone to Errors
if (score >= 60) {
    grade = "Passing Grade";
}
else  // Note: C++ else on a different line is valid, but team style is inconsistent
    grade = "Fail";

// Omission of a loop condition = an infinite loop
while (true) {  // Wanted 3 iterations, but forgot the counter
    // ... Didn't realize until 500 lines later
}

引き継いだ後、チャーリーはそれをGoで書き直すことにした。

(2) Go言語での解決策

Goでは、このプロセス全体を4つのキーワードで網羅しています:

GO
// score_evaluator.go
package main

import "fmt"

func evaluateScore(score int) string {
    // (1) if contains an initialization statement
    if grade := calculateGrade(score); grade != "" {
        return fmt.Sprintf("Score: %d, Level: %s", score, grade)
    }
    return "Invalid Score"
}

func calculateGrade(score int) string {
    switch {
    case score >= 90:
        return "A"
    case score >= 80:
        return "B"
    case score >= 60:
        return "C"
    default:
        return "D"
    }
}

func main() {
    // (2) for 4 forms
    scores := []int{95, 82, 67, 45, 88}

    for i, s := range scores {  // range form
        fmt.Printf("Question %d: %d points -> %s\n", i+1, s, evaluateScore(s))
    }
}

出力:

TEXT 📖 参照専用
Question 1: 95 points -> Score: 95, Level A
Question 2: 82 points -> Score: 82, Level B
Question 3: 67 points -> Score: 67, Level C
Question 4: 45 points -> Score: 45, Level D
Question 5: 88 points -> Score: 88, Level B

(3) メリット:プロセス制御の簡便性の比較

言語 if キーワード ループ キーワード switch の自動ブレーク 採点システムのコード量
C/C++ if for/while/do-while 手動での break が必要 約500行
Java if for/while/do-while 手動での break が必要 約400行
Python if for/while 該当なし(switch なし) 約350行
Go if for (4つの形式) 自動改行 約200行
💡 ヒント: Goはすべてのループをforに統一し、switchのデフォルトの挙動からbreakを削除します。キーワードが1つ減り、バグの種類も10種類減ります。



3. if文

(1) 基本的な if / else

GO
package main

import "fmt"

func main() {
    score := 85

    if score >= 90 {
        fmt.Println("Excellent")
    } else if score >= 60 {
        fmt.Println("Passing Grade")
    } else {
        fmt.Println("Fail")
    }
}

出力:

TEXT 📖 参照専用
Passing Grade
🔥 よくある間違い: Go では、else キーワードは } と同じ行に記述する必要があります(中括弧形式が必須です)。}else{ という構文では、コンパイルエラーが発生します。

▶ サンプル:成績判定

GO
package main

import "fmt"

func judgeGrade(score int) string {
    if score >= 90 {
        return "A"
    } else if score >= 80 {
        return "B"
    } else if score >= 70 {
        return "C"
    } else if score >= 60 {
        return "D"
    } else {
        return "F"
    }
}

func main() {
    fmt.Println(judgeGrade(95))  // A
    fmt.Println(judgeGrade(72))  // C
    fmt.Println(judgeGrade(50))  // F
}
▶ 試してみよう

出力:

TEXT 📖 参照専用
A
C
F

(3) if 初期化文(Go言語の機能)

if では、最初に初期化文を実行し、その後に条件チェックを行うことができます。これにより、変数のスコープを制限することができます:

GO
package main

import (
    "fmt"
    "strconv"
)

func main() {
    // Syntax: if initialization statement; condition { ... }
    // The variable `err` is scoped only within the `if-else` block.
    if n, err := strconv.Atoi("123"); err == nil {
        fmt.Printf("Conversion successful: %d\n", n)
    } else {
        fmt.Printf("Conversion failed: %v\n", err)
    }
}

出力:

TEXT 📖 参照専用
Conversion successful: 123

▶ サンプル:ファイルの読み込みと閉じ方 defer

GO
package main

import (
    "fmt"
    "os"
)

func readFile(filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer file.Close()  // automatically close file before function returns

    data := make([]byte, 1024)
    n, _ := file.Read(data)
    fmt.Printf("%d bytes read\n", n)

    return nil
}

func main() {
    readFile("config.txt")
}
▶ 試してみよう

(5) if ステートメントにおけるエラー処理の一般的な手法(Go言語の特徴)

Go言語におけるエラー処理は、if文を用いて行われます。一般的な使用法:

GO
if err := doSomething(); err != nil {
    return err  // early return
}
// Normal Logic
💡 ヒント: 第8課では、error 型について詳しく解説し、このパターンにおけるベストプラクティスについても説明します。



4. ブランチの切り替え(自動ブレーク)

(1) スイッチの基礎:自動ブレーク

デフォルトでは、Goのswitch文は伝播しません。つまり、いずれかのcaseに一致すると自動的に終了します:

GO
package main

import "fmt"

func main() {
    day := "Monday"

    switch day {
    case "Monday":
        fmt.Println("Monday: Meeting Day")
    case "Tuesday":
        fmt.Println("Tuesday: Development Day")
    case "Friday":
        fmt.Println("Friday: Demo Day")
    default:
        fmt.Println("Weekend: Day off")
    }
}

出力:

TEXT 📖 参照専用
Monday: Meeting Day

(2) C/Javaとの比較:breakは不要

言語 構文 自動改行
C/C++ case 1: ...; case 2: ... マニュアルが必要 break;
Java case 1: ...; case 2: ... 手動での操作が必要 break;
Go case "Monday": ... 自動改行

fall-through を使用したい場合は、fallthrough キーワードを使用してください:

GO
switch n {
case 1:
    fmt.Println("One")
    fallthrough  // force execution of the next case
case 2:
    fmt.Println("Two")
}

出力:

TEXT 📖 参照専用
One
Two

(3) 表現形式を切り替える(タグなし)

switch の後に変数を続けてはいけません。各 case の条件は個別に記述してください:

GO
package main

import "fmt"

func gradeLevel(score int) string {
    switch {
    case score >= 90:
        return "A"
    case score >= 80:
        return "B"
    case score >= 60:
        return "C"
    default:
        return "D"
    }
}

func main() {
    fmt.Println(gradeLevel(95))
    fmt.Println(gradeLevel(72))
}

出力:

TEXT 📖 参照専用
A
C

▶ サンプル:複数値の一致による切り替え + HTTP ステータスコードによるルーティング

GO
package main

import "fmt"

func routeStatus(code int) string {
    switch code {
    case 200, 201, 204:
        return "Success"
    case 301, 302:
        return "Redirect"
    case 400, 401, 403, 404:
        return "Client Error"
    case 500, 502, 503:
        return "Server Error"
    default:
        return "Unknown"
    }
}

func main() {
    fmt.Println(routeStatus(200))  // Success
    fmt.Println(routeStatus(404))  // Client Error
    fmt.Println(routeStatus(500))  // Server Error
}
▶ 試してみよう

出力:

TEXT 📖 参照専用
Success
Client Error
Server Error


5. forループの4つの形式

Go言語では、すべてのループがforという構文の下で統一されており、whiledo-while、および無限ループを実装するために異なる構文が使用されています。

(1) 古典的な3部構成の「for」ループ

GO
package main

import "fmt"

func main() {
    for i := 1; i <= 3; i++ {
        fmt.Printf("The %dth time\n", i)
    }
}

出力:

TEXT 📖 参照専用
The 1th time
The 2th time
The 3th time

▶ サンプル:「while」スタイル(条件付きループ)

GO
package main

import "fmt"

func main() {
    n := 1
    for n < 5 {  // equivalent to while (n < 5)
        fmt.Printf("n=%d\n", n)
        n++
    }
}
▶ 試してみよう

出力:

TEXT 📖 参照専用
n=1
n=2
n=3
n=4

(3) 無限ループ

GO
package main

import (
    "fmt"
    "time"
)

func main() {
    // Classic Infinite Loop (In real-world scenarios, it would use `break` or `return`)
    for {
        fmt.Println("Print once per second...")
        time.Sleep(1 * time.Second)
        break  // prevent actual infinite loop, for demo only
    }
}

出力:

TEXT 📖 参照専用
Print once per second...

▶ サンプル:range を使用してスライス、マップ、または文字列を反復処理する

GO
package main

import "fmt"

func main() {
    scores := []int{95, 82, 67}

    // Iterate through the slice: index, value
    for i, score := range scores {
        fmt.Printf("Problem %d: %d points\n", i+1, score)
    }

    // If only value is present: use _ to ignore index
    for _, score := range scores {
        fmt.Printf("Score: %d\n", score)
    }

    // Iterate through a map (key-value)
    ages := map[string]int{"Alice": 28, "Bob": 32}
    for name, age := range ages {
        fmt.Printf("%s is %d years old\n", name, age)
    }

    // Iterate over a string (by rune, not by byte)
    for i, r := range "Go" {
        fmt.Printf("Position %d: %c (code point %d)\n", i, r, r)
    }
}
▶ 試してみよう

出力(マップの順序はランダムです):

TEXT 📖 参照専用
Problem 1: 95 points
Problem 2: 82 points
Problem 3: 67 points
Score: 95
Score: 82
Score: 67
Alice is 28 years old
Bob is 32 years old
Position 0: G (code point 71)
Position 1: o (code point 111)
🔥 よくある間違い: range map の順序は保証されていません。実行のたびに異なる場合があります。一貫した順序が必要な場合は、反復処理を行う前にキーをソートしてください。

(5) 「for」の4つの用法に関するクイックリファレンス

形式 構文 代替案
標準 for i := 0; i < n; i++ {} C/Java for
「while」スタイル for condition {} C/Java while
インフィニット・ループ for {} while(true)
範囲の反復処理 for i, v := range slice {} Python for...in
100%
flowchart LR
    A[for init; condition; post] --> B{condition true?}
    B -->|yes| C[execute loop body]
    C --> D[execute post statement]
    D --> B
    B -->|no| E[exit loop]
    style A stroke-width:2px
    style E stroke:#f44,stroke-width:2px


6. defer:実行の延期(LIFO順序)

defer は、現在の関数が戻る直前に、ステートメントの実行を延期します。これは、リソースのクリーンアップ(ファイルの閉じ、リソースのロック解除、接続の切断など)によく使用されます。

(1) defer の基本

GO
package main

import "fmt"

func main() {
    fmt.Println("1: Start of function")

    defer fmt.Println("3: defer execution (before the function returns)")

    fmt.Println("2: Function Body")
}

出力:

TEXT 📖 参照専用
1: Start of function
2: Function Body
3: defer execution (before the function returns)

(2) LIFO(後入れ先出し)順序を延期する

GO
package main

import "fmt"

func main() {
    defer fmt.Println("1")
    defer fmt.Println("2")
    defer fmt.Println("3")

    fmt.Println("Main logic complete")
}

出力:

TEXT 📖 参照専用
Main logic complete
3
2
1

なぜLIFOなのか? それは、deferが「スタックベースのリソース管理」をシミュレートしているためです。つまり、最後に開かれたリソースが最初に閉じられます。

▶ サンプル:deferの実用例 — データベース接続の閉じ方

GO
package main

import (
    "database/sql"
    "fmt"
    "log"
)

func queryUser(db *sql.DB, userID int) {
    // Get the connection
    rows, err := db.Query("SELECT name FROM users WHERE id = ?", userID)
    if err != nil {
        log.Fatal(err)
    }

    // Key Point: `defer` releases resources (LIFO)
    defer rows.Close()  // Close result set when function returns

    // Business logic...
    for rows.Next() {
        var name string
        rows.Scan(&name)
        fmt.Printf("User: %s\n", name)
    }
}

func main() {
    fmt.Println("Example of Resource Management with `defer`")
    // Real-world scenario: db, _ := sql.Open("mysql", "...")
    // queryUser(db, 1)
}
▶ 試してみよう
💡 ヒント: return の途中で defer file.Close() が呼び出されても、defer file.Close() は実行されます。これは Go のリソース管理の代表的なユースケースです。注:この関数内で開いたリソースのクリーンアップは defer のみで行ってください。db が渡されており、そのライフサイクルは呼び出し元によって管理されているため、ここで defer db.Close() を行わないでください。



7. break / continue / label:正確なジャンプ

(1) break:ループを終了する

GO
package main

import "fmt"

func main() {
    for i := 1; i <= 10; i++ {
        if i == 5 {
            break  // exit the entire loop
        }
        fmt.Printf("%d ", i)
    }
    fmt.Println("End of loop")
}

出力:

TEXT 📖 参照専用
1 2 3 4 End of loop

▶ サンプル:スキップを継続し、奇数を1ずつ増やす

GO
package main

import "fmt"

func sumOdd(n int) int {
    sum := 0
    for i := 1; i <= n; i++ {
        if i%2 == 0 {
            continue  // skip even numbers
        }
        sum += i
    }
    return sum
}

func main() {
    fmt.Printf("1-10: Sum of odd numbers: %d\n", sumOdd(10))
}
▶ 試してみよう

出力:

TEXT 📖 参照専用
1-10: Sum of odd numbers: 25

(3) ネストされたループから抜け出すには label を使用する

label はループに名前を付けます。break label は指定されたループを終了します:

GO
package main

import "fmt"

func main() {
outer:
    for i := 1; i <= 3; i++ {
        for j := 1; j <= 3; j++ {
            if i*j == 6 {
                fmt.Printf("Hit: i=%d, j=%d\n", i, j)
                break outer  // directly exit the outer loop
            }
            fmt.Printf("(%d, %d) ", i, j)
        }
    }
    fmt.Println("Done")
}

出力:

TEXT 📖 参照専用
(1, 1) (1, 2) (1, 3) (2, 1) (2, 2) (2, 3) (3, 1) (3, 2) Hit: i=3, j=2
Done

(4) 3種類のリダイレクトの比較

キーワード 機能 対象外
break 現在のループまたはswitch文を終了する 単一レベル
continue この反復の残りをスキップ 単一レベル
break label 指定されたラベルでループを終了 ネスト


8. 完全な例:オンライン試験採点システム

このレッスンで学んだすべての制御フローを連携させ、多次元試験採点システムを実装しましょう:

GO
// exam_evaluator.go
package main

import "fmt"

// Structure of the Problem
type Question struct {
    Type    string  // single/multi/judge
    Correct string  // correct answer
    Student string  // student answer
    Score   float64 // score value
}

// Scoring Function: Initialized with an `if` statement + `switch` expression + `for range`
func evaluateExam(name string, questions []Question) (total float64, details []string) {
    defer func() {
        // defer: Collect results (ensures that results are collected even if a return statement is encountered midway)
        details = append(details, fmt.Sprintf("[%s] Total score: %.1f", name, total))
    }()

    for i, q := range questions {
        var earned float64

        // switch expression format: multi-condition branching
        switch q.Type {
        case "single":
            if q.Student == q.Correct {
                earned = q.Score
            }
        case "judge":
            if q.Student == q.Correct {
                earned = q.Score
            } else {
                earned = 0
            }
        case "multi":
            // Multiple-choice question: Points are awarded only for exact matches
            if q.Student == q.Correct {
                earned = q.Score
            } else {
                earned = 0
            }
        default:
            continue  // skip unknown question type
        }

        total += earned
        details = append(details,
            fmt.Sprintf("Problem %d (%s): Scored %.1f points", i+1, q.Type, earned))
    }

    return total, details
}

func main() {
    questions := []Question{
        {Type: "single", Correct: "B", Student: "B", Score: 2},
        {Type: "judge", Correct: "true", Student: "true", Score: 1},
        {Type: "multi", Correct: "A,B,C", Student: "A,B", Score: 3},
        {Type: "single", Correct: "D", Student: "D", Score: 2},
    }

    // Use a `for range` loop to iterate through the student list
    students := []string{"Alice", "Bob", "Charlie"}
    for _, name := range students {
        total, details := evaluateExam(name, questions)

        fmt.Println("========================================")
        for _, d := range details {
            fmt.Println(d)
        }
    }
}

期待される出力(各学生の詳細な成績+成績概要):

TEXT 📖 参照専用
========================================
Problem 1 (single): Scored 2.0 points
Problem 2 (judge): Scored 1.0 points
Problem 3 (multi): Scored 0.0 points
Problem 4 (single): Scored 2.0 points
[Alice] Total score: 5.0
========================================
Problem 1 (single): Scored 2.0 points
Problem 2 (judge): Scored 1.0 points
Problem 3 (multi): Scored 0.0 points
Problem 4 (single): Scored 2.0 points
[Bob] Total score: 5.0
========================================
Problem 1 (single): Scored 2.0 points
Problem 2 (judge): Scored 1.0 points
Problem 3 (multi): Scored 0.0 points
Problem 4 (single): Scored 2.0 points
[Charlie] Total score: 5.0
🔥 よくある間違い: 外側のスコープから参照できるようにするには、defer 内の名前付き戻り値を修正する必要があります。上記の例では、details が名前付き戻り値 (total float64, details []string) を使用しているため、defer 内の append が有効になります。


❓ よくある質問

Q なぜGoにはwhileというキーワードがないのですか?
A Goでは、すべてのループにforを使用します。for condition {}whileと同等であり、for {}while(true)と同等です。キーワードが1つ少ないことで、チームはwhiledo-whileのどちらを使うかについて議論する必要がなくなります。
Q switch がデフォルトでフォールスルーしないのは面倒ではないですか?
A それどころか、Go チームは switch のケースの 90% は自己完結的であるべきだと考えています。C や Java における「break の記述を忘れてフォールスルーを引き起こしてしまう」という問題は、典型的なバグの一つです。Goでは、fallthroughを使用してフォールスルーする意図を明示的に示すため、より安全です。
Q for range で反復処理を行うと、マップの順序がランダムになるのはなぜですか?
A Go は、プログラマーがマップの順序に依存しないように、意図的に順序付けされていない設計になっています。一貫した順序が必要な場合は、反復処理の前にキーをソートしてください:keys := make([]string, 0, len(m)); for k := range m { keys = append(keys, k) }; sort.Strings(keys)
Q defer はいつ実行されますか?複数の defer ステートメントの実行順序はどのようになりますか?
A defer は、現在の関数が戻る前に実行されます(つまり、return ステートメントが実行された後、関数が実際に終了する前です)。複数の defer ステートメントは LIFO(後入れ先出し)順に実行され、スタックベースのリソース管理をシミュレートします。つまり、スタックに最後にプッシュされたリソースが最初に解放されます。
Q if 初期化文のスコープはどのようになりますか?
A if 初期化文で宣言された変数は、if-else ブロック内でのみ参照可能であり、if-else ブロックを離れると無効になります。これにより、「変数が外側のスコープに漏れ出す」というよくあるバグを防ぐことができます。
Q labelは実際のプロジェクトでよく使われますか?
A めったに使われません。labelは主にネストされたループから抜け出すために使われますが、99%のケースでは、breakreturnを組み合わせれば十分です。labelを使いすぎるとコードの可読性が低下します。これはgotoの時代から学んだ教訓です。
Q Goのswitchという記述は型チェックに対応していますか?
A はい。switch x := y.(type)はインターフェースの型チェックに使用されます。これについては第7課で詳しく説明します。ただし、この構文はinterface{}型の変数にのみ適用されます。
Q continuebreakswitch ステートメント内で使用できますか?
A はい、使用できますが、動作が異なります。breakswitch ステートメントを終了させますが、for ループ内では for ループを終了させます。continueforループ内でのみ使用でき、switchステートメント内では使用できません(switchはループとは見なされないため)。

📖 まとめ


📝 練習問題

  1. 基本問題(難易度 ⭐)forswitch を使用して FizzBuzz プログラムを作成してください。1 から 100 までの数字を出力します。3の倍数に遭遇した場合は「Fizz」を、5の倍数に遭遇した場合は「Buzz」を、15の倍数に遭遇した場合は「FizzBuzz」を出力してください。

  2. 上級問題(難易度 ⭐⭐):1 から n までの間の素数の個数を返す関数 countPrimes(n int) int を実装してください。要件: (1) forループを使用すること;(2) if文を使用して素数であるかどうかを判定すること;(3) 関数が戻る前に、deferを使用して実行時間(time.Now()を使用)を出力すること。

  3. チャレンジ問題(難易度 ⭐⭐⭐):学生の成績解析ツールを実装してください。一連の得点 ([]float64) が与えられたとき、(1) 平均得点、(2) 合格率(60点以上)、 (3) 最高点と最低点;(4) 成績別内訳(A/B/C/D/F)。要件:(1) for range を使用すること;(2) switch ステートメントを使用して成績を判定すること;(3) 関数が戻る前に defer を使用して要約情報を出力すること。

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%