メインコンテンツまでスキップ

第3章:if文地獄を“わざと”体験😵‍💫🔥(状態機械が必要になる理由を体感する回)

今日はあえて「よくあるダメ実装」を作って、どこで破綻するかを体で覚える章だよ〜🧨 ここでしんどさを味わうほど、次の章以降がスッと入る…!🥹✨

(最新メモ🧠)2026年1月20日時点では、.NET 10 は 2025年11月11日にリリースされ、LTSとしてサポートされます。公式のサポート表では .NET 10 の最新パッチは 10.0.2(2026年1月13日)で、サポート終了は 2028年11月14日とされています。(Microsoft) C# 14 の新機能は Visual Studio 2026 または .NET 10 SDK で試せる、と公式ドキュメントに明記されています。(Microsoft Learn)


この章のゴール🎯✨

終わったらこうなってればOK💮

  • 「フラグ(bool)だらけの実装」が なぜ壊れやすいか を説明できる🗣️
  • 学食注文フローを if/else で書いて、破綻点を3つ 見つけられる🔎
  • 「次は状態機械でやりたい…」って心から思える😇🔥

まず結論:フラグ地獄の正体👻

003 Comparison

boolフラグを増やすと、状態の組み合わせが爆発します💥

  • フラグが7個なら、組み合わせは 2^7 = 128通り😵‍💫
  • でも実際にあり得るのは、そのうちのほんの一部
  • 残りは 「矛盾状態(バグ状態)」 として普通に作れてしまう…🧨

例:

  • 「ReadyなのにCookingじゃない」🍳❓
  • 「CancelledなのにPickedUp」🛑➡️📦(は?ってなるやつ)
  • 「RefundedなのにPaidじゃない」💸❓

演習①:わざと“if文地獄版”を作ろう😵‍💫🔥

1) クラスを作る(フラグ乱立バージョン)🚩🚩🚩

下のコードをそのまま貼って実行してみてね(わざとツッコミどころ多め)😇

using System;

public sealed class OrderIfHell
{
// 状態を bool で持つ(←今日の主役:地獄の入口)
public bool IsDraft { get; private set; } = true;
public bool IsSubmitted { get; private set; }
public bool IsPaid { get; private set; }
public bool IsCooking { get; private set; }
public bool IsReady { get; private set; }
public bool IsPickedUp { get; private set; }
public bool IsCancelled { get; private set; }
public bool IsRefunded { get; private set; }

public DateTime? PaidAt { get; private set; }
public int? PaidAmount { get; private set; }

public void Submit()
{
if (IsCancelled)
{
Console.WriteLine("❌ Cancel済みなのでSubmitできません");
return;
}

if (IsSubmitted)
{
Console.WriteLine("⚠️ もうSubmit済みです");
return;
}

// Draft→Submitted のつもり
IsSubmitted = true;

// ⚠️ わざと:IsDraftを落とし忘れる(矛盾の種🌱)
Console.WriteLine("✅ Submitしました");
}

public void Pay(int amount)
{
if (!IsSubmitted)
{
Console.WriteLine("❌ まだ注文してないのに支払いはできません");
return;
}

if (IsCancelled)
{
Console.WriteLine("❌ Cancel済みなので支払いできません");
return;
}

if (IsPaid)
{
Console.WriteLine("⚠️ 二重決済っぽいです(もうPaid)");
return;
}

IsPaid = true;
PaidAt = DateTime.Now;
PaidAmount = amount;

Console.WriteLine($"✅ Payしました amount={amount}");
}

public void StartCooking()
{
if (!IsPaid)
{
Console.WriteLine("❌ 未払いなので調理開始できません");
return;
}

if (IsCooking)
{
Console.WriteLine("⚠️ もう調理中です");
return;
}

if (IsCancelled)
{
Console.WriteLine("❌ Cancel済みなので調理開始できません");
return;
}

IsCooking = true;
Console.WriteLine("🍳 調理開始しました");
}

public void MarkReady()
{
// ⚠️ わざと:Cookingチェックが弱い(Readyだけ先に立てられる芽🌱)
if (!IsPaid)
{
Console.WriteLine("❌ 未払いなのでReadyにできません");
return;
}

if (IsReady)
{
Console.WriteLine("⚠️ もうReadyです");
return;
}

IsReady = true;
Console.WriteLine("📣 Readyになりました");
}

public void PickUp()
{
if (!IsReady)
{
Console.WriteLine("❌ Readyじゃないので受け取れません");
return;
}

if (IsCancelled)
{
Console.WriteLine("❌ Cancel済みなので受け取れません");
return;
}

IsPickedUp = true;
Console.WriteLine("📦 受け取り完了しました");
}

public void Cancel()
{
if (IsCancelled)
{
Console.WriteLine("⚠️ もうCancel済みです");
return;
}

// ⚠️ よくある:条件が増えるほどここが育っていく…
if (IsPickedUp)
{
Console.WriteLine("❌ 受け取った後はキャンセルできません");
return;
}

IsCancelled = true;
Console.WriteLine("🛑 Cancelしました");
}

public void Refund()
{
if (!IsCancelled)
{
Console.WriteLine("❌ CancelしてないのでRefundできません");
return;
}

if (!IsPaid)
{
Console.WriteLine("❌ 支払いがないのでRefundできません");
return;
}

if (IsRefunded)
{
Console.WriteLine("⚠️ もうRefund済みです");
return;
}

IsRefunded = true;

// ⚠️ わざと:Paidを落とす?落とさない?が曖昧(仕様が混ざるやつ)
Console.WriteLine("💸 Refundしました");
}

public void Dump(string title)
{
Console.WriteLine($"--- {title} ---");
Console.WriteLine($"Draft={IsDraft}, Submitted={IsSubmitted}, Paid={IsPaid}, Cooking={IsCooking}, Ready={IsReady}, PickedUp={IsPickedUp}, Cancelled={IsCancelled}, Refunded={IsRefunded}");
Console.WriteLine();
}
}

public static class Program
{
public static void Main()
{
var o = new OrderIfHell();

o.Dump("初期");

o.Submit();
o.Dump("Submit後");

o.Pay(800);
o.Dump("Pay後");

// ここ、順序を変えるとどうなる?😈
o.MarkReady();
o.Dump("Ready後");

o.StartCooking();
o.Dump("Cooking後");

o.Cancel();
o.Dump("Cancel後");

o.Refund();
o.Dump("Refund後");

o.PickUp();
o.Dump("PickUp後");
}
}

演習②:破綻点を3つ探す🔎🧨(この章のメイン!)

上のコード、実行ログとDumpを見ながら、次のどれかを最低3つ見つけてね🫶

破綻点候補(見つけやすい順)🎯

  • 矛盾状態が作れる:Draft と Submitted が両方 true のまま…😇
  • 順序依存バグ:Ready を先に立てられる(Cooking前なのにReady)🍳❌📣
  • キャンセル後の扱いがグチャる:CancelしたのにReadyやCookingが残る/残らない問題🌀
  • 仕様が増えると修正箇所が増殖:Cancel条件、Pay条件、PickUp条件…あちこちに同じ話が散らばる💣
  • 例外ケースが“穴”として残る:「Cancel後にPickUp」みたいな変な順を完全に塞ぐのが大変😵‍💫

提出用メモの書き方(超おすすめ)📝✨

  • 破綻点A:どういう手順で起きた?(例:Pay→Ready→StartCooking)
  • 破綻点B:なにが“おかしい状態”?(例:Ready=true なのに Cooking=false)
  • 破綻点C:直そうとするとどこを触る?(例:MarkReadyとStartCookingとCancel…)
  • そして最後に一言:「つらい!」(大事)🥹🔥

「if文地獄」チェックリスト✅👀(このサイン出たら危険)

当てはまるほど、状態機械が恋しくなるよ💘

  • 状態が bool フラグで3つ以上ある🚩🚩🚩
  • 「本当は同時に成り立たない状態」がコード上は成り立つ😇
  • 1つ仕様追加するだけで、複数メソッドを修正する羽目になる🛠️
  • 条件が増えて、if がネストして読めなくなる🧵🧵
  • “禁止の理由”が散らばって、仕様が見えない🫥

ここまでのオチ(次の章への伏線)🎬✨

今日やったのは、

現在の状態」が1個に決まってないから、 「矛盾」も「抜け漏れ」も「分岐爆発」も起きちゃう

っていう体験でした😵‍💫

次の章以降で、これを

  • 現在状態を 1つにする(enumとか)
  • イベント(操作)を受け取って
  • 遷移を 表/図/ルール で管理する

みたいに整えていくよ🗺️📊✨


AI活用(この章)🤖🕵️‍♀️✨

Copilot / Codex に、こう聞くとめちゃ効くよ!

1) バグりやすい箇所レビューしてもらう🔍

  • 「この OrderIfHell クラス、状態の矛盾が起きるパターンを5つ挙げて。手順付きで」

2) “壊れるテスト手順”を作らせる🧪

  • 「このクラスを壊す操作シーケンスを10個作って。各シーケンスの期待と実際のズレも書いて」

3) “修正したくなる誘惑”を可視化する💣

  • 「仕様追加:Cancelは調理開始前のみOK、ReadyはCooking後のみOK。修正箇所と影響範囲を列挙して」

※ポイント:AIの指摘は便利だけど、最後に自分でDump見て納得するのが勝ち筋だよ〜🫶✨


今日のミニ課題(超大事)🎒💖

✅ 提出物(自分メモでOK)

  • 破綻点を3つ(手順+おかしい理由+直すならどこ?)📝
  • 「if文地獄のつらさ」一言コメント🥹🔥

必要なら、あなたの“破綻点メモ3つ”を貼ってくれたら、 次の章に行く前に「それ、状態機械だとどう解決できるか」もセットで優しく整理するよ😊🫶✨