状態爆発を防ぐステートマシン設計
組み込みソフトウェアの開発では、装置の動作を状態と遷移の組み合わせとして説明する「ステートマシン設計」が広く使われます。しかし機能を追加するたびに条件分岐が増え、収拾がつかなくなる例も珍しくありません。こうした問題の背景には、「状態爆発」と呼ばれる状態数の急増が挙げられます。
本記事は有限状態機械(FSM)を実装するエンジニアに向けた解説です。設計が破綻する理由から、状態爆発を防ぐ基本設計、実装方式の選び方、テストとレビューの勘所までを順に取り上げます。
ステートマシン設計が破綻する理由
ステートマシン設計がうまくいかなくなる背景にあるのは、いくつかの共通した原因です。ここでは実装上の問題、状態爆発の典型的なパターン、そして組み込みでFSMが欠かせない理由という3つの観点から掘り下げます。
条件分岐を中心とした実装上の問題
状態の管理を条件分岐だけに頼ると、状態遷移の流れがコード全体へと分散します。すると、仕様変更のときに影響範囲を追いきれず、改修のたびに不具合が生じやすくなります。ネストが深くなれば、どの条件でどの状態へ移ったのかも読み取りにくくなるでしょう。組み込みでは通信やタイマー、エラー処理の追加によって複雑さがさらに増します。
状態爆発が起きる典型的なパターン
状態爆発が起きるのは、一つのFSMに役割を詰め込みすぎたときです。通信とエラー、タイマーの状態を単一のFSMで管理しようとすると、組み合わせの数だけ状態が増えます。状態と処理が密に結び付くと、一部の機能だけを取り出して再利用できません。例外処理が重なり通常系と異常系が混ざると、遷移条件はさらに膨らみます。
組み込みシステムではFSMが重要
ここまで課題を見てきましたが、FSMそのものが悪いわけではありません。破綻の多くは使い方に原因があり、正しく設計すれば組み込みソフトウェアの開発において強力な武器になります。FSMは割り込みやイベント駆動と相性が良く、リアルタイム制御の流れを整理しやすい手法です。状態遷移を明示すれば動作仕様を関係者と共有でき、状態を単位としたテストの計画も立てやすくなります。
状態爆発を防ぐ基本的な設計
状態爆発を防ぐには、設計の段階で状態の増加を抑える工夫が欠かせません。鍵を握るのは、状態とイベントの分離、状態遷移表による管理、そして階層化の3つです。
状態とイベントを分離させる
状態爆発を抑える第一歩は、状態とイベントを別々の概念として扱うことです。状態は装置の現在のモードを指し、イベントは状態が変わる契機を指します。両者を分けてイベント駆動型にすると、条件分岐を一箇所へ集めやすくなります。受け取ったイベントを一度キューにためる方式なら、割り込み処理との切り分けも容易です。
状態遷移表による管理
状態遷移表は、状態とイベントの組み合わせを表の形で整理する手法です。縦軸に状態、横軸にイベントを並べ、交わるセルに処理の内容や遷移先を書き込みます。すべての組み合わせが一覧になるため、定義漏れを目視で確認可能です。未定義のイベントが起きたときの動作も明確にできます。また状態遷移表は、試験項目を洗い出すための重要な資料となります。テストを行う際に、状態遷移図を見ながら、その遷移を正常系、異常系合わせて確認することは組み込み開発では必須です。
階層化により状態数を削減
階層化は、複数の状態に共通する動作を上位の状態としてまとめる手法です。たとえばエラー状態や待機状態への遷移を上位状態に一度だけ定義すれば、個々の状態で同じ記述を繰り返さずにすみます。共通動作を集約すると、状態の総数の増加を抑えられます。複雑な遷移パターンも局所に閉じ込めることができ、コード全体の可読性が高まるでしょう。
実装のパターン別の特徴と使い分け
設計方針が決まったら、次はFSMをどう実装するかを選びます。代表的な方式には3つの型があり、扱う状態の規模によって向き不向きが分かれます。小規模から大規模まで、順に特徴を見ていきましょう。
switch-case型のFSM
switch-case型は、状態ごとの処理をswitch文で振り分ける実装方式です。コードの構造が単純で、状態ごとの処理を一覧として読み取りやすい利点があります。一方で状態が増えるとcase文が膨らみ、見通しが悪くなります。小規模な制御やプロトタイプの開発に向いた方式といえます。
テーブル駆動型のFSM
テーブル駆動型は、先ほどの状態遷移表をそのままデータとしてコードに落とし込む方式です。遷移のルールが配列や構造体に集約され、処理ロジックと切り離して管理できます。テスト時には遷移の網羅性を確かめやすく、仕様変更のときも修正の範囲を限定しやすくなります。switch-case型では見通しが悪くなる中規模以上の場面に適するでしょう。
関数分離・オブジェクト型のFSM
関数分離やオブジェクト型は、状態ごとに独立した関数やクラスを割り当てる方式です。状態の処理が分かれているため、単体テストのときにモック化しやすくなります。新しい状態を追加しても影響範囲が局所にとどまり、大規模な開発に適しています。リアルタイムOSでタスクを状態ごとに分ける構成とも相性良くなじみます。
テストしやすいFSM実装のポイント
FSMの実装では、設計の質と同じくらいテストのしやすさが問われます。押さえたいのは、イベント駆動によるテスト、割り込み処理での注意点、そして状態遷移ログの活用です。
イベント駆動によるスムーズなテスト
状態の変更をイベント駆動に統一すると、テストとデバッグが進めやすくなります。状態が変わる箇所がイベントの受け取り口に集まり、どこを確認すべきかがはっきりするでしょう。テストケースもイベント単位で整理でき、抜けにくくなります。記録したイベントのログをたどれば、不具合が起きた状況を再現する試験も実施しやすくなります。
ISR内ではイベント通知のみ実行
割り込みハンドラの中で状態を直接変更すると、デバッグの難易度が大きく上がります。そこでISR(割り込みサービスルーチン)の役割はイベントの通知だけに絞り、状態の管理はメインタスクへ集約することが推奨されます。ISRは発生したイベントをキューへ渡す処理に専念させるとよいでしょう。この分担により、リアルタイム性とコードの読みやすさの両立が可能となります。
状態遷移ログの活用
状態遷移ログは、現在の状態とイベント、遷移先を記録として残す仕組みです。障害が起きたとき、このログをたどれば状態がどの順で移ったのかを追跡できます。長時間の試験で発生する異常を再現したい場面でも、ログは手がかりになります。テスト結果と遷移の記録を対応づけられるため、原因の切り分けがスムーズになるでしょう。
FSMのレビューにおいて確認すべきポイント
実装が終わったら、設計の段階で防ぎきれなかった問題をレビューで捕まえます。レビューは状態爆発や遷移漏れを最後に発見する工程です。具体的には、遷移の抜け漏れ、異常系イベント、命名規則の3点を確認します。
状態遷移の抜け漏れの確認
レビューで最初に確かめるのは、状態とイベントのすべての組み合わせに遷移が定義されているかです。未定義のイベントを受け取ったときの動作が決まっているか、異常系の遷移に漏れがないかを見ていきます。状態遷移表と実装の内容が一致しているかも重要な確認点です。あわせて、不要な状態や重複した状態が残っていないかも点検します。
異常系イベントの確認
異常系イベントの確認で対象になるのは、通信の遮断やタイムアウト、リトライの失敗といった事象への対応です。異常が起きた後、どの遷移をたどって復帰するのかを明確に決めておきます。装置を安全な状態へ移すフェイルセーフの遷移条件も確認が必要です。例外処理を足したことで状態爆発が起きていないかも、あわせて見ておきます。
保守が容易な命名規則
コードの読みやすさは、命名規則に大きく左右されます。状態名は装置の動作を表す言葉で、イベント名は発生した事象を表す言葉で統一しましょう。状態名とイベント名の粒度をそろえると、表やコードの見通しが良くなります。コメントに頼らず構造と名前だけで意図が伝わる状態は、状態が増えても破綻しにくい設計につながります。


