ステートマシンとは?状態遷移の設計と実装パターン・採用判断を実装者目線で解説

ステートマシン(状態機械)は、システムが取りうる「状態」と、状態を切り替える「遷移」の集合でふるまいを記述する設計モデルです。注文処理、決済フロー、通信プロトコル、UIの画面遷移、ゲームのキャラクター制御まで、条件分岐が絡み合う処理を整理する土台になります。この記事では、有限オートマトンの基礎、状態遷移表・状態遷移図による表現、enum+switch・Stateパターン・テーブル駆動・XStateなどの実装方式、AWS Step Functionsを使った分散ワークフロー、そして「どこで採用し、どこで見送るか」の判断基準までを、実装者の視点で順にたどります。

目次

まとめ:ステートマシンは分岐の複雑さを状態と遷移へ構造化する道具

ステートマシンの核は4つの要素です。取りうる状態(state)、状態を切り替える遷移(transition)、遷移のきっかけになるイベント(event)、遷移時に走るアクション(action)。この4つで「いま何ができて、次に何が起きるか」を明示的に閉じ込めます。

実装の入口は言語標準のenumと分岐で足ります。状態ごとの処理が膨らむならStateパターンでクラスに分け、遷移規則を宣言的に持ちたいならテーブル駆動が向きます。堅牢な状態管理が要るならXStateのようなライブラリへ進むとよいでしょう。複数サービスにまたがる長時間ワークフローは、AWS Step Functionsのようなマネージドな状態機械に載せると、失敗時の再試行や可視化までインフラ側で引き受けられます。

ただし万能ではありません。状態が2〜3個で分岐も単純なら、素朴な条件分岐のほうが読みやすい。導入判断は「状態数が増えて遷移の抜け漏れが怖くなったか」を境目にします。以下で理論・表現・実装・分散・採用判断の順に掘り下げます。

ステートマシンの基礎となる状態・遷移・有限オートマトンの考え方

まず用語とモデルを固めます。ステートマシンは計算機科学の「オートマトン」を実務に落とした設計手法で、数学的な裏付けを持つぶん、ふるまいを厳密に検証できるのが強みです。

状態・遷移・イベント・アクションというステートマシンの4要素

ステートマシンは4要素で定義します。状態はシステムがとどまる局面(例:注文の「未払い」「支払済」「発送済」)。イベントは外部・内部から届く刺激(「入金通知」「発送指示」)。遷移は「ある状態で特定イベントを受けたとき、どの状態へ移るか」の規則。アクションは遷移や状態の出入りに伴う副作用(メール送信、在庫引当)です。

ここで肝になるのが、遷移を「現在状態×イベント」の組でしか定義しないという制約です。未払いの注文に「発送指示」が来ても、そこに遷移規則が無ければ何も起きない。分岐を書き足すのではなく、許可した遷移だけが成立する。この閉じた世界が、状態管理の抜け漏れを防ぎます。

有限オートマトン(FSM)とムーア型・ミーリー型の出力の違い

状態の数が有限のステートマシンを有限オートマトン(Finite State Machine、FSM)と呼びます。実務で扱うほぼすべてがこのFSMです。FSMは出力の出し方で2系統に分かれます。

出力が決まるタイミング 向いている用途
ムーア型(Moore) 現在の状態だけで出力が決まる 状態と表示が1対1で対応(信号機の色・UI画面)
ミーリー型(Mealy) 現在の状態と入力の組み合わせで出力が決まる 入力ごとに反応を変える(プロトコル処理)

ムーア型は状態が増えやすい代わりに、出力が状態から一意に読めるため理解しやすい。ミーリー型は少ない状態で済むが、同じ状態でも入力で挙動が変わるため追いにくくなります。UIや設定画面はムーア型、入力駆動の変換処理はミーリー型、と最初の型を決めておくと設計がぶれません。

状態遷移表と状態遷移図というステートマシンの2つの表現手段の違い

設計の共有には2つの表現を使い分けます。状態遷移表は縦に状態、横にイベントを取り、交点に「遷移先の状態」を書く格子です。空欄は「その組み合わせは起きない(禁止遷移)」を意味し、埋め残しがそのまま仕様の穴として見えます。レビューで漏れを潰すのに向きます。

状態\イベント 入金通知 発送指示 キャンセル
未払い 支払済 取消済
支払済 発送済 返金待ち
発送済

一方の状態遷移図は状態を丸、遷移を矢印で描く図で、全体の流れを直感的につかむのに向きます。UMLでは「ステートマシン図(状態遷移図)」として標準化されており、同じ振る舞い図の仲間であるシーケンス図のUML表記と併用すると、時系列と状態の両面から設計を検証できます。表で漏れを潰し、図で流れを合意する。この二段構えが実務での定石です。

ステートマシンが解決するシステム設計上の課題と導入すべき動機

なぜわざわざ状態機械という枠を導入するのか。素朴な条件分岐で書いたコードが壊れていく過程を見ると、導入の動機がはっきりします。

フラグ変数の増殖とif文の分岐爆発をステートマシンの型で止める

状態をbooleanの組で表すと、破綻は早い。isPaidisShippedisCancelledの3フラグは論理的に8通りの組み合わせを生みますが、実際に成立するのは数通りだけ。残りは「支払済かつ未払い」のような、あってはならない状態です。フラグが増えるほど、この「存在してはいけない組み合わせ」をコード中のifで毎回排除する羽目になります。

ステートマシンは、取りうる状態をenumで列挙し、成立しない組み合わせを型として表現不可能にします。「支払済かつ未払い」は書こうにも書けない。バグの温床を、コードレビューではなく型で消せるのが実利です。

不正な状態遷移を実行時やコンパイル時に機械的にはじく仕組みづくり

状態を1つの変数に集約すると、遷移の入口を1箇所に絞れます。「現在状態でこのイベントは許可されているか」を遷移関数で一元判定し、許可外なら例外を投げるか、無視して現状維持にする。発送済の注文に再度の発送指示が来ても、遷移表に規則が無ければ処理は進みません。

この防御は、注文・決済・予約のようにお金や在庫が動く処理で効きます。二重発送や二重課金は、多くが「本来ありえない遷移をコードが許してしまった」ことが原因です。遷移をデータとして持ち、規則外を機械的に弾く構造にしておくと、境界条件のテストも「許可された遷移」と「禁止された遷移」の2軸で書き切れます。

ステートマシンの代表的な実装パターンと開発規模ごとの使い分け

実装には段階があります。小さく始めて、状態や遷移の数が増えたら次の方式へ移ると、過剰設計を避けられます。代表的な4方式を、採用の目安をつけて以下に整理しました。

enumとswitchによる素朴なステートマシンの実装と適用範囲

状態が数個、遷移も単純なら、enumで状態を定義し、遷移関数の中でswitchする形が最短です。追加ライブラリも設計知識も要らず、状態が1変数に集約されるだけで前述のフラグ増殖はほぼ防げます。

目安は状態5個・遷移10本あたりまで。switchのネストが二重三重になり、状態ごとの処理が長くなってきたら、次のStateパターンへ切り替える合図です。まずここから始めて、痛みが出てから昇格するのが健全な順序になります。

Stateパターン(GoF)で状態ごとにクラスを分ける実装方式

GoFデザインパターンのStateパターンは、状態ごとにクラスを作り、状態が持つべき振る舞いをそのクラスに閉じ込めます。コンテキストは現在の状態オブジェクトへ処理を委譲し、遷移は「保持する状態オブジェクトを差し替える」ことで表現する形です。switchが消え、状態ごとの責務がファイル単位で分かれるため、状態内のロジックが厚いときに読みやすくなります。

代償はクラス数の増加です。状態が10個あればクラスも10個。状態ごとの処理が薄いのにクラスだけ増えると、かえって見通しが悪くなります。「各状態が固有の複雑な振る舞いを持つか」を採否の分かれ目にしてください。振る舞いが薄いなら、次のテーブル駆動のほうが軽い。

状態遷移テーブルを宣言的に持つテーブル駆動という実装アプローチ

遷移規則を「現在状態・イベント・遷移先・アクション」のデータ構造(配列やマップ)として外に出し、エンジンはそのテーブルを引くだけにする方式です。ロジックとデータが分離するため、遷移の追加は表への1行追加で済み、前掲の状態遷移表とコードがほぼ1対1で対応します。仕様書と実装の乖離が起きにくいのが最大の利点です。

状態や遷移が数十規模まで膨らんでも、テーブルは平坦なまま保てます。反面、条件分岐を伴う複雑なガード(「在庫があるときだけ遷移」など)を表に押し込むと、テーブルが読みにくくなる。ガードが多い設計では、次のライブラリが用意する専用構文に頼るほうが素直です。

XStateなどのステートマシンライブラリを導入する際の判断

階層状態・並行状態・ガード条件・遅延イベントまで扱うなら、専用ライブラリが近道です。フロントエンドのTypeScript/JavaScriptではXStateが代表格で、状態チャート(Statechart)としてネストした状態や並行領域を宣言的に書け、可視化ツールで図と実装を同期できます。.NETにはstateless、JavaのSpringエコシステムにはSpring StateMachineがあり、いずれもガードやエントリ/エグジットアクションを標準機能として持ちます。

実装方式 導入コスト 向く規模 弱点
enum+switch 最小 状態〜5・遷移〜10 状態内ロジックが厚いと肥大化
Stateパターン 状態ごとに固有の厚い振る舞い クラス数が状態数ぶん増える
テーブル駆動 状態・遷移が数十規模 複雑なガードを表に押し込むと崩れる
ライブラリ(XState等) やや高 階層・並行・ガードが必要 学習コストと依存の追加

ライブラリは強力ですが、状態が3個の画面遷移にXStateを持ち込むのは過剰です。「階層状態か並行状態が本当に要るか」を導入前に自問してください。要らないなら、テーブル駆動で十分に間に合います。

分散システムにおけるステートマシンとマネージドな実装の選択肢

1プロセス内の話から視野を広げると、複数サービスにまたがる業務フロー自体が巨大なステートマシンです。ここではインフラ側が状態機械を肩代わりする選択肢が出てきます。

AWS Step Functionsでマネージドなワークフローを構築する

AWS Step Functionsは、状態遷移をJSONベースの定義(Amazon States Language)で記述し、各状態でLambdaやその他サービスを呼び出すマネージドなステートマシンです。分岐(Choice)、並列(Parallel)、再試行(Retry)、待機(Wait)が状態として標準化されており、失敗時のリトライやタイムアウト、実行履歴の可視化をアプリ側で作り込まずに済みます。長時間のバッチ、承認をはさむ業務フロー、複数APIを順に叩くオーケストレーションが主戦場です。

マネージドな状態機械を業務システムに組み込む設計は、状態定義の粒度・エラーハンドリング・コスト(状態遷移課金)の見極めが成否を分けます。AWSを含むクラウドインフラの設計・構築支援では、Step Functionsを用いたワークフローの状態設計から運用まで、実装者の視点で伴走します。自前実装かマネージドかで迷う段階からご相談ください。

イベント駆動アーキテクチャとステートマシンそれぞれの役割分担

ステートマシンの「イベントで遷移する」性質は、イベント駆動アーキテクチャと相性が良い。メッセージブローカーから届くイベントを遷移のトリガーにすれば、疎結合なサービス群を1つの業務状態機械として束ねられます。遷移のたびにイベントを発行すれば、他サービスがそれを購読して次の処理を始める連鎖も組めます。

この設計の全体像は、イベント駆動アーキテクチャの仕組みと実装パターンの解説で扱っています。状態機械は「1つのサービスが持つ状態の管理」、イベント駆動は「サービス間の連携方式」と役割が分かれ、両者を組み合わせると分散業務フローの見通しが立ちます。

サーガパターンと長時間トランザクションの進行管理への応用方法

複数サービスをまたぐトランザクションは、DBの単一トランザクションでは守れません。そこで各サービスのローカル処理を順に実行し、失敗したら補償処理(逆操作)で巻き戻すサーガパターンを使います。このサーガの進行管理そのものが、状態機械で表現するのに向いた対象です。オーケストレーション型サーガでは、中央のステートマシンが「どこまで進んだか」「どこから補償するか」を状態として保持します。

サーガや長時間ワークフローは処理が非同期になり、応答を待たずに次へ進む設計が前提になります。この土台は非同期処理の実装方式の解説にまとめました。状態機械が「進行の管理」、非同期処理が「待たずに進める仕組み」を担い、両輪で分散ワークフローが成立します。

ステートマシンの採用判断:採用する場面と見送るべき場面の境目

ここは玉虫色にせず言い切ります。ステートマシンは強力な整理術ですが、常に正解ではありません。導入の損益分岐を条件で示します。

ステートマシンを素朴な条件分岐から採用すべき3つの判断の条件

次のいずれかに当てはまるなら、素朴な分岐から状態機械へ昇格する価値があります。第一に、状態が明確に3個以上あり、状態ごとに許可される操作が違う場合。第二に、二重課金・二重発送のように、不正な遷移が金銭や在庫の損失に直結する場合。第三に、状態遷移の仕様が非エンジニアとの合意対象になり、表や図で共有する必要がある場合です。

とりわけ決済・予約・注文・承認フローは、状態と遷移が業務ルールそのものであり、状態機械にすると仕様変更への追従が「表の書き換え」に収まります。ドメインの状態を厳密に扱う設計はドメイン駆動設計の考え方とも接続し、集約のライフサイクルを状態機械として表すと、業務ロジックの置き場所が定まります。

ステートマシンが過剰設計になり採用を見送るべき典型的な3場面

逆に、次の場合はステートマシンを持ち込むべきではありません。状態が2個以下で、遷移も「オンとオフ」程度の一本道なら、if一行のほうが読みやすい。ここに遷移テーブルやライブラリを入れると、抽象の層だけが増えて理解の妨げになります。

また、状態が実質的に無く、単なる逐次処理(手順1→2→3を上から実行するだけ)にステートマシンをかぶせるのも過剰です。それは状態機械ではなく、ただのフローチャートを重装備で書いているにすぎません。「戻る・分岐する・特定状態でしか許されない操作がある」という要素が無いなら、状態機械の恩恵は出ないと判断してください。迷ったら、まずenum+switchで小さく始め、痛みが出た時点で昇格する順序が安全です。

よくある質問

ステートマシンの設計・実装で実装者から挙がりやすい質問をまとめます。

ステートマシンと状態遷移図は同じものですか?

別のレイヤーの言葉です。ステートマシンは「状態と遷移でふるまいを記述する設計モデル(概念・実装)」を指し、状態遷移図はそのモデルを丸と矢印で描いた「表現手段(図法)」を指す言葉です。同じ状態機械を、状態遷移表でも状態遷移図でも表せます。UMLではこの図を「ステートマシン図」と呼びます。

有限オートマトンとステートマシンの違いは何ですか?

有限オートマトン(FSM)は、状態の数が有限であるステートマシンを指す、より厳密な計算モデルの呼び名です。実務で扱うステートマシンはほぼすべて状態数が有限なので、両者はほぼ同義に使われます。理論では入力を受理するかどうかを判定する「受理器」としての側面が強調され、実装では処理を制御する枠として語られる、という力点の違いがあります。

ステートマシンはどの言語で実装できますか?

言語を選びません。enumと分岐があればどの言語でも実装でき、状態を1変数に集約する発想自体は言語非依存です。加えてTypeScript/JavaScriptのXState、.NETのstateless、JavaのSpring StateMachineのように、主要言語には専用ライブラリが揃っています。まず標準機能で書き、規模が増えたらライブラリへ移るのが定石です。

状態が増えて状態遷移表が巨大になったらどうすればよいですか?

階層状態(Statechart)で分割します。関連する状態を親状態にまとめ、共通の遷移を親側へ引き上げると、表の行数を抑えられるのが利点です。XStateなどのライブラリはこの階層状態と並行状態を標準で扱えるため、平坦なテーブルが破綻し始めたタイミングが、ライブラリ導入の目安になります。

ステートマシンの状態はデータベースに保存すべきですか?

永続化が必要な業務フローでは保存します。注文や予約のように再起動をまたいで状態が続くものは、現在状態をカラムとして保持し、遷移のたびに書き換えるのが基本です。AWS Step Functionsのようなマネージドな仕組みを使う場合は、実行状態の永続化と履歴保持をサービス側が引き受けるため、アプリ側での状態保存は不要になります。

関連記事

資料請求

RELATED POSTS 関連記事