Day 3: 条件分岐 - 解説
学習の目的
このDayで達成すること
- 条件に応じた処理の分岐ができる
- 比較演算子と論理演算子を使える
- 複数条件の処理ができる
---
学習の手引き
推奨学習順序
- 背景知識を読む(15分)
- 課題1に取り組む(30分)
- 課題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型の基礎
条件式は必ずtrueかfalseのbool型になります。
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:
&&と||、どちらが優先される?
&&が優先(AND > OR)- Q: 短絡評価とは?
- Q:
if x := 5; x > 0のxのスコープは?
応用力チェック
- Q: うるう年判定で
year%4==0を最初に判定すると?
- Q: FizzBuzzで新規ルール(7の倍数で"Woof")を追加するには?
- Q: 以下のコードの出力は?
if false && fmt.Println("A") == nil {
fmt.Println("B")
}
A: 何も出力されない(短絡評価により"A"も出力されない)実務応用チェック
- Q: 成績判定システムで基準変更が頻繁な場合の最適設計は?
- Q: 条件分岐が10個以上になった場合の対処法は?
- Q: nil チェックと短絡評価を組み合わせる理由は?
---
明日への準備
Day 4のテーマ
「ループ(for文)」を学びます。
予習コード:
for i := 0; i < 5; i++ {
fmt.Println(i)
}
考えてみよう:
- このコードは何回実行される?
iの値はどう変化する?i < 5をi <= 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: 必要最小限の実装
- [ ] if-else文が書ける
- [ ] 比較演算子を理解している
- [ ] 論理演算子(&&, ||)を使える
- [ ] else ifで多分岐ができる
- [ ] 短絡評価を理解している
- [ ] if文内で変数を宣言できる
- [ ] FizzBuzzが実装できる
- [ ] うるう年判定が実装できる
- Day 4: ループ制御をマスター
- 応用: 条件分岐とループの組み合わせ
- 実践: 実務レベルのバリデーションロジック
- 発展: ポリモーフィズムによる条件分岐の置き換え
今日のキーワード
| 用語 | 意味 | 重要度 |
|---|---|---|
| 条件分岐 | 条件に応じて処理を分ける | ★★★ |
| 真偽値 | true/falseの2値 | ★★★ |
| 比較演算子 | 値を比較する演算子 | ★★★ |
| 論理演算子 | 条件を組み合わせる演算子 | ★★★ |
| 短絡評価 | 効率的な条件評価 | ★★☆ |
| スコープ | 変数の有効範囲 | ★★☆ |
| DRY原則 | 重複を避ける設計 | ★★☆ |