第11章:アクション(Entry / Exit / Transition)入門🎬😊
この章は「状態が変わる瞬間に、何をする?」をスッキリ整理できるようになる回だよ〜✨ (次の第12章で「副作用を分離してテストしやすくする」につなげる準備回でもあるよ🧹💖)
11.1 この章のゴール🎯✨
できるようになることはコレ👇
- ✅ Entry / Exit / Transition の違いを説明できる🗣️
- ✅ 仕様の「〜したら○○する」を、どのアクションに置くべきか判断できる🧠
- ✅ “置き場所ミス”で起きる事故(通知二重送信とか💥)を避けられる🚧
11.2 まず超イメージで掴もう🍙📱💡
題材はいつもの「学食モバイル注文」だよ〜😊
状態がこう変わるとするね👇 Draft → Submitted → Paid → Cooking → Ready → PickedUp (+ Cancelled / Refunded など)
ここで大事なのは👇
- 状態が変わるときにやること(=アクション)
- 状態の中にいる間に起きること(※これは次の章以降で強化✨)
11.3 Entry / Exit / Transition って何?(超やさしく)🌷

✅ Entry Action(入った瞬間にやる)🚪✨
「その状態に 入った瞬間 に1回やること」
例:
- Paid に入ったら レシート発行🧾
- Cooking に入ったら 調理開始ログ🍳
- Ready に入ったら 受け取り通知📣
✅ Exit Action(出る瞬間にやる)🚶♀️✨
「その状態から 出る瞬間 に1回やること」
例:
- Cooking を出るときに タイマー停止⏱️
- Submitted を出るときに “未処理”フラグ解除🧹
✅ Transition Action(移動の“途中”でやる)🔁✨
「A→B に移る“その遷移”にくっつく処理」 UMLでも「遷移に effect を持てる」感じで説明されるよ〜📘✨ (sparxsystems.com)
例:
- Submitted → Paid の遷移で 決済IDを確定して保存💾
- Ready → PickedUp の遷移で 受取時刻を確定🕒
ざっくり覚え方🌟 Entry/Exitは“状態に所属”、Transitionは“移動に所属” だよ😊
11.4 置き場所の決め方:3秒ルール⏱️✨
迷ったら、この3つで決めるよ👇
- “その状態にいる”ことが条件? → Entry/Exit(状態の所属)
- “A→B の移動”が条件? → Transition(遷移の所属)
- 同じ到達先でも、入ってきた経路でやることが違う? → Transition に寄せる or 「どこから来たか」を見て Entry を分ける(あとで例出すね💖)
11.5 具体例で練習しよ🎮✨(学食フロー版)
例1:Paidになったらレシート発行🧾
- 状態:Paid に“入ったら”やる → Entry(Paid) が自然💡
例2:Submitted→Paid になった瞬間に「決済ID」を保存💾
- 「Submitted→Paid の遷移が起きた」ことが条件 → Transition(Submitted→Paid) が自然💡
例3:Cookingを出るときに「調理中フラグ」を消す🧹
- Cooking “から出る”が条件 → Exit(Cooking) 💡
例4:「Ready に入ったら通知」だけど、
- Cooking→Ready は通常通知
- Paid→Ready(何かの復元処理)では通知したくない😇 → 遷移ごとに違うから、Transition に置く方が事故りにくい✨
11.6 最小コードで“動き”を見よう👀✨(2パターン)
ここでは「雰囲気が分かればOK」だよ😊 (ガチ実装は15章以降で強化するよ🔧✨)
パターンA:まずは自作で “フック” を作る🪝✨
「遷移する直前/直後に呼ぶ場所」を用意して、Entry/Exit/Transition を体感するやつ💡
public enum OrderState { Draft, Submitted, Paid, Cooking, Ready, PickedUp, Cancelled }
public enum OrderEvent { Submit, Pay, StartCooking, MarkReady, Pickup, Cancel }
public sealed class OrderMachine
{
public OrderState State { get; private set; } = OrderState.Draft;
public void Fire(OrderEvent ev)
{
var from = State;
var to = Next(from, ev);
// Exit(出る瞬間)
OnExit(from, ev, to);
// Transition(移動の途中)
OnTransition(from, ev, to);
State = to;
// Entry(入る瞬間)
OnEntry(to, ev, from);
}
private static OrderState Next(OrderState from, OrderEvent ev) => (from, ev) switch
{
(OrderState.Draft, OrderEvent.Submit) => OrderState.Submitted,
(OrderState.Submitted, OrderEvent.Pay) => OrderState.Paid,
(OrderState.Paid, OrderEvent.StartCooking) => OrderState.Cooking,
(OrderState.Cooking, OrderEvent.MarkReady) => OrderState.Ready,
(OrderState.Ready, OrderEvent.Pickup) => OrderState.PickedUp,
(_, OrderEvent.Cancel) => OrderState.Cancelled,
_ => throw new InvalidOperationException($"禁止遷移: {from} + {ev}")
};
private void OnEntry(OrderState entered, OrderEvent by, OrderState from)
{
if (entered == OrderState.Paid) IssueReceipt(); // 🧾 Entry例
if (entered == OrderState.Ready) NotifyReady(); // 📣 Entry例(※経路差があるならTransitionへ)
}
private void OnExit(OrderState exiting, OrderEvent by, OrderState to)
{
if (exiting == OrderState.Cooking) StopCookingTimer(); // ⏱️ Exit例
}
private void OnTransition(OrderState from, OrderEvent by, OrderState to)
{
if (from == OrderState.Submitted && to == OrderState.Paid) SavePaymentId(); // 💾 Transition例
WriteTransitionLog(from, by, to); // 📜 ここに置くと分かりやすいこと多いよ
}
private static void IssueReceipt() { /* ... */ }
private static void NotifyReady() { /* ... */ }
private static void StopCookingTimer() { /* ... */ }
private static void SavePaymentId() { /* ... */ }
private static void WriteTransitionLog(OrderState from, OrderEvent by, OrderState to) { /* ... */ }
}
このコードの良いところは👇 「Entry/Exit/Transition を置く場所の感覚」が育つこと🌱✨
パターンB:ライブラリで“教科書みたいに”書く📚✨(Stateless)
Stateless は .NET で有名な状態機械ライブラリで、Entry/Exit などがそのまま書けるよ〜😊 最新版は NuGet で **5.20.0(2025-09-18 公開)**が確認できるよ。 (NuGet) (GitHubのREADMEにも OnEntry/OnExit 例が載ってるよ📘) (GitHub)
using Stateless;
public enum OrderState { Draft, Submitted, Paid }
public enum OrderTrigger { Submit, Pay }
var sm = new StateMachine<OrderState, OrderTrigger>(OrderState.Draft);
sm.Configure(OrderState.Draft)
.Permit(OrderTrigger.Submit, OrderState.Submitted);
sm.Configure(OrderState.Submitted)
.OnEntry(() => Console.WriteLine("Submittedに入ったよ📨"))
.Permit(OrderTrigger.Pay, OrderState.Paid);
sm.Configure(OrderState.Paid)
.OnEntry(() => Console.WriteLine("Paidに入ったよ🧾✨"))
.OnExit(() => Console.WriteLine("Paidから出るよ🚪"));
sm.Fire(OrderTrigger.Submit);
sm.Fire(OrderTrigger.Pay);
11.7 よくある事故💥(ここ超大事😇)
事故1:通知が二重に飛ぶ📣📣
例:
- Paid の Entry に「レシートメール」
- Submitted→Paid の Transition にも「レシートメール」 → 2回送信しがち😵💫
💡対策:
- “レシート送信”は どっちか一箇所に決める
- 「経路で変えたい」なら Transition に寄せる が安全✨
事故2:OnEntry 内で次の遷移を発火して混乱する🌀
Stateless でも「OnEntry中に Fire() したい」ケースの議論があるよ。 (GitHub) (気持ちは分かるけど、読みづらく&順序が混乱しやすい😇)
💡対策:
- 「自動で次へ進む」は、外側(アプリ層)でイベントを投げる
- もしくは「処理中(Processing)」みたいな中間状態を設計(24章で強化⏳)
事故3:Entry/Exit が“重い処理”の塊になる🐘
通知・DB・外部APIが全部入り始めると、テスト地獄へ😵💫 → 次の12章で “ロジック vs I/O” を分けてスッキリさせるよ🧹✨
11.8 ミニ演習🎮🧠(仕分けゲーム)
次の「〜したら○○する」を、Entry / Exit / Transition のどれに置くか考えてね👇✨
- Paidになったらレシートを発行する🧾
- Submitted→Paid になったら決済IDを確定して保存する💾
- Cookingを出るときに調理タイマーを止める⏱️
- Readyに入ったら通知する📣(ただし復元時は通知しない)
- Cancelledに入ったら返金処理を開始する💸(※実際のI/Oは次章で分離ね✨)
解答例(まずは自分の答え出してから見てね😆💖)
-
- Entry(Paid)
-
- Transition(Submitted→Paid)
-
- Exit(Cooking)
-
- Transition(Cooking→Ready の方に寄せる)
-
- Entry(Cancelled)(ただし“返金API呼び出し”は次章で外に逃がす予定🧹)
11.9 AI活用コーナー🤖✨(Copilot / Codex)
そのまま貼って使えるプロンプト例だよ〜💌
-
🧠 アクション候補の洗い出し 「学食注文の状態機械で、Entry/Exit/Transitionに置くと良いアクション候補を、重複しないように列挙して。各項目に理由も1行つけて。」
-
🔍 二重送信・二重保存の匂いチェック 「このアクション一覧を見て、同じ副作用(通知・保存・課金)が複数箇所に置かれていないか指摘して。統合案も出して。」
-
🧾 仕様の言い換え 「‘Paidになったらレシート発行’を、誰が読んでも誤解しない仕様文に3パターン(短い/普通/丁寧)で書いて。」
11.10 まとめ🎀✨(次章へのつながり)
- Entry/Exit/Transition は “所属”で決める(状態?遷移?)
- “経路で違う”なら Transition に寄せると事故りにくい✨
- アクションは増えるほど危ないので、次章で 副作用を分離して強くするよ🧹💖
ちなみに、.NET 10 は 2025-11-11 にリリースされて、LTSで2028-11-10までサポートって公式に出てるよ〜🪄 (Microsoft for Developers) (この講座のコードもその前提で育てていけるね😊)
次は第12章「副作用の分離(ロジック vs I/O)」だね🚪📤✨ この第11章のアクションを“仕分け”して、テストしやすい設計にしていくよ〜🧪💖