Day 3: 条件分岐 - 解説

学習の目的

このDayで達成すること

  • 条件に応じた処理の分岐ができる
- if-else文の基本構文をマスター - プログラムに「判断」をさせられる - コンピュータに意思決定の仕組みを実装

  • 比較演算子と論理演算子を使える
- 値の比較(>, <, ==など)の正確な理解 - 条件の組み合わせ(&&, ||)の活用 - 複雑な条件式の構築

  • 複数条件の処理ができる
- else ifによる多分岐 - 条件の優先順位を理解 - 効率的な条件評価の設計

---

学習の手引き

推奨学習順序

  • 背景知識を読む(15分)
- 条件分岐の基本概念 - 演算子の種類と使い方

  • 課題1に取り組む(30分)
- 単純な条件分岐の実装 - if-else文の基礎固め

  • 課題2に取り組む(40分)
- 複数条件の処理 - 論理演算子の活用

  • 解答と比較(15分)
- 自分の実装との差分確認 - 代替アプローチの学習

効果的な学習のコツ

実験的アプローチ

  • 条件の値を変えて結果を確認
  • 境界値(60点、20歳など)での動作検証
  • 異常値での挙動確認

探索的学習

  • 条件の順序を入れ替えてみる
  • else ifの順序変更が結果に与える影響を観察
  • エラーになるコードを意図的に書いてみる

デバッグスキル

// fmt.Printfでデバッグ出力を活用
if score >= 60 {
    fmt.Printf("[DEBUG] score=%d, condition: score >= 60 is true\n", score)
    fmt.Println("合格")
}

---

概念の深掘り

条件式の評価

bool型の基礎

条件式は必ずtruefalsebool型になります。

package main

import "fmt"

func main() {
    x := 5

    // 比較演算の結果はbool型
    fmt.Printf("x > 3 = %v (型: %T)\n", x > 3, x > 3)     // true (型: bool)
    fmt.Printf("x == 3 = %v (型: %T)\n", x == 3, x == 3)  // false (型: bool)
    fmt.Printf("x < 3 = %v (型: %T)\n", x < 3, x < 3)     // false (型: bool)
}

真偽値表

演算子  意味              例 (x=5)    結果
==     等しい            x == 5      true
!=     等しくない        x != 5      false
<      未満              x < 10      true
>      より大きい        x > 10      false
<=     以下              x <= 5      true
>=     以上              x >= 5      true

論理演算子の評価順序

優先順位のルール

&&(AND)は||(OR)より優先されます。

package main

import "fmt"

func main() {
    a, b, c := true, false, false

    // a || b && c は a || (b && c) と同じ
    result1 := a || b && c
    fmt.Println("true || false && false =", result1)  // true

    // 意図と異なる場合は括弧で明示
    result2 := (a || b) && c
    fmt.Println("(true || false) && false =", result2)  // false
}

優先順位表

優先度  演算子  名称
1       !       NOT(論理否定)
2       &&      AND(論理積)
3       ||      OR(論理和)

複雑な条件式の例

// 条件: (成人 かつ 学生) または VIP会員
age := 25
isStudent := false
isVIP := true

// 括弧なし(優先順位に依存)
result := age >= 20 && isStudent || isVIP  // true

// 括弧あり(意図を明確化)
result := (age >= 20 && isStudent) || isVIP  // true

短絡評価(Short-circuit Evaluation)

メカニズム

&&||短絡評価されます。

package main

import "fmt"

// expensiveCheck: 重い処理をシミュレート
func expensiveCheck() bool {
    fmt.Println("expensiveCheck() が呼ばれました")
    return true
}

func main() {
    // && の短絡評価
    fmt.Println("=== && の例 ===")
    if false && expensiveCheck() {
        // expensiveCheck()は呼ばれない(左がfalse)
    }

    // || の短絡評価
    fmt.Println("=== || の例 ===")
    if true || expensiveCheck() {
        // expensiveCheck()は呼ばれない(左がtrue)
    }
}

出力:

=== && の例 ===
=== || の例 ===

実務での活用例

// nil チェックと短絡評価の組み合わせ
var user *User

// ✅ 安全: userがnilの場合、user.IsAdmin()は呼ばれない
if user != nil && user.IsAdmin() {
    fmt.Println("管理者です")
}

// ❌ 危険: nilポインタエラーが発生する可能性
if user.IsAdmin() && user != nil {
    // user が nil の場合、パニック発生
}

パフォーマンス最適化

// コストの低い条件を先に評価
// ✅ 効率的
if cheapCheck() && expensiveCheck() {
    // cheapCheck()がfalseなら、expensiveCheck()は実行されない
}

// ❌ 非効率
if expensiveCheck() && cheapCheck() {
    // 常にexpensiveCheck()が実行される
}

---

アルゴリズム分析

時間計算量

単純な条件分岐

if score >= 60 {
    fmt.Println("合格")
} else {
    fmt.Println("不合格")
}

時間計算量: O(1)

  • 条件評価: O(1)
  • 分岐処理: O(1)
  • 結論: 定数時間

多段階条件分岐

if score >= 90 {
    fmt.Println("A")
} else if score >= 80 {
    fmt.Println("B")
} else if score >= 70 {
    fmt.Println("C")
} else if score >= 60 {
    fmt.Println("D")
} else {
    fmt.Println("不可")
}

時間計算量: O(1)

  • 最悪ケース: 5回の比較(全ての条件がfalse)
  • 平均: 2.5回の比較
  • 結論: 条件数が固定なら O(1)

条件数が n の場合: O(n)

// n個の条件がある場合
for i := 0; i < len(thresholds); i++ {
    if score >= thresholds[i].MinScore {
        // ...
        break
    }
}

空間計算量

基本的な条件分岐

if age >= 20 {
    fmt.Println("成人です")
}

空間計算量: O(1)

  • 追加のメモリ使用なし

データ駆動型アプローチ

thresholds := []GradeThreshold{
    {90, "A"},
    {80, "B"},
    {70, "C"},
    {60, "D"},
}

空間計算量: O(n)

  • n: 基準の数

---

設計原則

SOLID原則の適用

1. Single Responsibility(単一責任原則)

// ❌ 悪い例: 複数の責任が混在
func processScore(score int) {
    // 判定ロジック
    grade := ""
    if score >= 90 {
        grade = "A"
    } else if score >= 80 {
        grade = "B"
    }

    // 表示ロジック(別の責任)
    fmt.Printf("成績: %s\n", grade)

    // データベース保存(さらに別の責任)
    saveToDatabase(grade)
}

// ✅ 良い例: 責任を分離
func calculateGrade(score int) string {
    if score >= 90 {
        return "A"
    } else if score >= 80 {
        return "B"
    }
    return "不可"
}

func displayGrade(grade string) {
    fmt.Printf("成績: %s\n", grade)
}

func main() {
    score := 85
    grade := calculateGrade(score)
    displayGrade(grade)
}

2. Open/Closed(開放閉鎖原則)

// ✅ 拡張に開いている、変更に閉じている
type GradeCalculator interface {
    Calculate(score int) string
}

type StandardGradeCalculator struct {
    thresholds []GradeThreshold
}

func (c *StandardGradeCalculator) Calculate(score int) string {
    for _, threshold := range c.thresholds {
        if score >= threshold.MinScore {
            return threshold.Grade
        }
    }
    return "不可"
}

// 新しい計算ロジックを追加(既存コードを変更しない)
type LenientGradeCalculator struct {
    // 甘めの基準
}

func (c *LenientGradeCalculator) Calculate(score int) string {
    // 異なる基準で計算
}

DRY原則(Don't Repeat Yourself)

// ❌ 悪い例: 重複コード
if num1%2 == 0 {
    fmt.Printf("%dは偶数です\n", num1)
} else {
    fmt.Printf("%dは奇数です\n", num1)
}

if num2%2 == 0 {
    fmt.Printf("%dは偶数です\n", num2)
} else {
    fmt.Printf("%dは奇数です\n", num2)
}

// ✅ 良い例: 関数化
func printEvenOdd(n int) {
    if n%2 == 0 {
        fmt.Printf("%dは偶数です\n", n)
    } else {
        fmt.Printf("%dは奇数です\n", n)
    }
}

func main() {
    printEvenOdd(num1)
    printEvenOdd(num2)
}

KISS原則(Keep It Simple, Stupid)

// ❌ 複雑: 必要以上に凝った実装
func isLeapYear(year int) bool {
    return (year%400 == 0) || (year%4 == 0 && year%100 != 0)
}

// ✅ シンプル: 読みやすい実装
func isLeapYear(year int) bool {
    if year%400 == 0 {
        return true
    }
    if year%100 == 0 {
        return false
    }
    if year%4 == 0 {
        return true
    }
    return false
}

トレードオフ:

  • 複雑版: 1行、数学的に正確
  • シンプル版: 読みやすい、初学者向け
  • 推奨: チームのレベルに応じて選択

YAGNI原則(You Aren't Gonna Need It)

// ❌ 過剰設計: 現時点で不要な機能
type GradeSystem struct {
    calculator GradeCalculator
    logger     Logger
    cache      Cache
    validator  Validator
    // 実際には使わない機能が多数
}

// ✅ 必要最小限
func calculateGrade(score int) string {
    if score >= 60 {
        return "合格"
    }
    return "不合格"
}

---

メンタルモデル

条件分岐の思考プロセス

1. フローチャートで考える

      開始
       |
       v
   [score入力]
       |
       v
  <score >= 60?>
       |
    Yes|  No
       |   |
       v   v
     合格 不合格
       |   |
       v   v
      終了

2. 決定木(Decision Tree)

成績判定の決定木:

           score
             |
        score >= 90?
       /            \
     Yes             No
      |               |
      A          score >= 80?
                /            \
              Yes             No
               |               |
               B          score >= 70?
                         /            \
                       Yes             No
                        |               |
                        C          score >= 60?
                                  /            \
                                Yes             No
                                 |               |
                                 D              不可

3. 真理値表で考える

FizzBuzzの真理値表:

i%3==0  i%5==0  出力
false   false   数値
false   true    Buzz
true    false   Fizz
true    true    FizzBuzz

ビジュアル図解(ASCII Art)

if-else の実行フロー

┌─────────────┐
│   if文開始   │
└──────┬──────┘
       │
       v
┌──────────────┐
│  条件評価    │
│  age >= 20?  │
└──┬────────┬──┘
   │        │
 true     false
   │        │
   v        v
┌─────┐  ┌─────┐
│成人 │  │未成年│
└──┬──┘  └──┬──┘
   │        │
   └────┬───┘
        v
   ┌─────────┐
   │   終了   │
   └─────────┘

if-else if-else の実行フロー

┌─────────────┐
│    開始     │
└──────┬──────┘
       v
┌──────────────┐
│ score >= 90? │
└──┬────────┬──┘
 true      false
   │          │
   v          v
  [A]   ┌──────────────┐
        │ score >= 80? │
        └──┬────────┬──┘
         true      false
           │          │
           v          v
          [B]   ┌──────────────┐
                │ score >= 70? │
                └──┬────────┬──┘
                 true      false
                   │          │
                   v          v
                  [C]   ┌──────────────┐
                        │ score >= 60? │
                        └──┬────────┬──┘
                         true      false
                           │          │
                           v          v
                          [D]       [不可]

短絡評価の可視化

false && someFunction()

   false
     ↓
   [停止] → 右辺は評価されない
     ↓
   false(結果)


true || someFunction()

   true
     ↓
   [停止] → 右辺は評価されない
     ↓
   true(結果)

---

よくある質問(FAQ)

Q1: =と==の違いは?

A:

  • =: 代入(値を入れる)
  • ==: 比較(等しいかどうか)

// 代入
x := 5      // xに5を代入(宣言と同時)
x = 10      // xに10を代入(既存変数)

// 比較
x == 5      // xが5と等しいか判定 → true/false

よくある間違い:

// ❌ C言語的な間違い(Goではコンパイルエラー)
if x = 5 {  // エラー: cannot use x = 5 as value
    fmt.Println("x is 5")
}

// ✅ 正しい
if x == 5 {
    fmt.Println("x is 5")
}

Q2: ifの中で変数を宣言できますか?

A: はい、できます。この変数のスコープはif文内に限定されます。

// if文内で宣言(推奨パターン)
if x := getValue(); x > 0 {
    fmt.Println(x)  // OK
}
// xはif文の外では使えない

// スコープの可視化
func main() {
    if x := 10; x > 0 {
        fmt.Println(x)  // OK: 10

        if y := 20; y > 0 {
            fmt.Println(x)  // OK: 10(外側のスコープ)
            fmt.Println(y)  // OK: 20
        }
        // fmt.Println(y)  // エラー: yは定義されていない
    }
    // fmt.Println(x)  // エラー: xは定義されていない
}

実務での活用例:

// エラーハンドリング
if err := doSomething(); err != nil {
    return fmt.Errorf("failed: %w", err)
}

// ファイル読み込み
if data, err := os.ReadFile("file.txt"); err == nil {
    fmt.Println(string(data))
}

Q3: switch文とif-else if、どちらを使うべき?

A: 状況に応じて使い分けます。

// ✅ switch が適切: 値の列挙
switch month {
case 3, 4, 5:
    return "春"
case 6, 7, 8:
    return "夏"
}

// ✅ if-else if が適切: 複雑な条件
if age >= 20 && hasLicense {
    return "運転可能"
} else if age >= 18 && hasParentalConsent {
    return "条件付き運転可能"
}

Q4: 浮動小数点数の比較は危険?

A: はい、丸め誤差があるため直接比較は避けるべきです。

import "math"

// ❌ 危険
var x float64 = 0.1 + 0.2
if x == 0.3 {  // false(丸め誤差)
    fmt.Println("equal")
}

// ✅ 安全: 誤差範囲で比較
const epsilon = 1e-9
if math.Abs(x - 0.3) < epsilon {
    fmt.Println("equal")
}

Q5: 条件が多すぎる場合の対処法は?

A: リファクタリングパターンを適用します。

// ❌ 悪い例: 条件が10個以上
if condition1 {
} else if condition2 {
} else if condition3 {
// ... 10個以上続く
}

// ✅ パターン1: switch文
switch {
case condition1:
case condition2:
case condition3:
}

// ✅ パターン2: マップ
actions := map[string]func(){
    "case1": func1,
    "case2": func2,
}
actions[key]()

// ✅ パターン3: ポリモーフィズム(interface)
type Handler interface {
    Handle()
}

---

セルフチェック質問

基礎理解度チェック

  • Q: 以下のコードの出力は?
   x := 10
   if x%2 == 0 {
       fmt.Println("A")
   } else {
       fmt.Println("B")
   }
   
A: "A"(10は偶数)

  • Q: &&||、どちらが優先される?
A: &&が優先(AND > OR)

  • Q: 短絡評価とは?
A: 左辺の評価で結果が確定したら、右辺を評価しない仕組み

  • Q: if x := 5; x > 0xのスコープは?
A: if文のブロック内のみ

応用力チェック

  • Q: うるう年判定でyear%4==0を最初に判定すると?
A: 1900年などが誤判定される(100で割り切れる年の考慮漏れ)

  • Q: FizzBuzzで新規ルール(7の倍数で"Woof")を追加するには?
A: 文字列連結方式が最も拡張しやすい

  • Q: 以下のコードの出力は?
   if false && fmt.Println("A") == nil {
       fmt.Println("B")
   }
   
A: 何も出力されない(短絡評価により"A"も出力されない)

実務応用チェック

  • Q: 成績判定システムで基準変更が頻繁な場合の最適設計は?
A: マップまたは構造体配列で基準を外部化

  • Q: 条件分岐が10個以上になった場合の対処法は?
A: switch文、マップ、ポリモーフィズム(interface)を検討

  • Q: nil チェックと短絡評価を組み合わせる理由は?
A: nilポインタアクセスによるパニックを防ぐため

---

明日への準備

Day 4のテーマ

「ループ(for文)」を学びます。

予習コード:

for i := 0; i < 5; i++ {
    fmt.Println(i)
}

考えてみよう:

  • このコードは何回実行される?
  • iの値はどう変化する?
  • i < 5i <= 5に変えると何が起こる?

予習課題:

// このコードの出力を予測してみよう
for i := 1; i <= 10; i++ {
    if i%2 == 0 {
        fmt.Println(i)
    }
}

---

まとめ

今日学んだこと

1. 基本構文

  • if文: if condition { }
  • if-else文: 二択の分岐
  • if-else if-else文: 多分岐

2. 演算子

  • 比較演算子: ==, !=, <, >, <=, >=
  • 論理演算子: &&(AND)、||(OR)、!(NOT)

3. 重要概念

  • 短絡評価: 効率的な条件評価
  • スコープ: if文内での変数宣言
  • 優先順位: 演算子の評価順序

4. 設計原則

  • SOLID: 単一責任、開放閉鎖
  • DRY: 重複排除
  • KISS: シンプルさの追求
  • YAGNI: 必要最小限の実装
  • 今日のキーワード

    用語 意味 重要度
    条件分岐 条件に応じて処理を分ける ★★★
    真偽値 true/falseの2値 ★★★
    比較演算子 値を比較する演算子 ★★★
    論理演算子 条件を組み合わせる演算子 ★★★
    短絡評価 効率的な条件評価 ★★☆
    スコープ 変数の有効範囲 ★★☆
    DRY原則 重複を避ける設計 ★★☆

    達成度チェックリスト

  • [ ] if-else文が書ける
  • [ ] 比較演算子を理解している
  • [ ] 論理演算子(&&, ||)を使える
  • [ ] else ifで多分岐ができる
  • [ ] 短絡評価を理解している
  • [ ] if文内で変数を宣言できる
  • [ ] FizzBuzzが実装できる
  • [ ] うるう年判定が実装できる
  • 次のステップ

  • Day 4: ループ制御をマスター
  • 応用: 条件分岐とループの組み合わせ
  • 実践: 実務レベルのバリデーションロジック
  • 発展: ポリモーフィズムによる条件分岐の置き換え