組み込みシステムはリソースが制限されており、信頼性が最も重要である世界で動作します。🌍 マイコンやリアルタイムオペレーティングシステム向けのソフトウェアを設計する際、論理はしばしば明確な動作モードを中心に展開されます。デバイスは起動し、入力を待機し、データを処理した後、スリープ状態に入ることがあります。これらの遷移をクリーンに管理することは、極めて重要です。
状態機械図(SMD)は、統一モデリング言語(UML)の一部であり、この動作の視覚的ブループリントを提供します。しかし、図の質は、それを表すコードの質に依存します。🧱 このガイドでは、保守性が高く、堅牢な組み込みコードに直接対応する状態機械図の設計におけるベストプラクティスを説明します。

📋 状態機械が組み込み設計において果たす役割の理解
構文やレイアウトに飛び込む前に、なぜ状態機械がスパゲッティコードや複雑なネストされたものよりも好まれるのかを理解することが不可欠です。if-else文よりも好まれるのかを理解することが不可欠です。主な目的は決定論性です。
- 予測可能性:現在の状態と入力イベントが与えられれば、次の状態は常に定義される。
- トレーサビリティ:エンジニアは、システムが外部刺激にどのように反応するかを視覚的に追跡できる。
- 保守性:新しい状態を追加するか、遷移を変更する際は局所的に行えるため、関係のない機能を破壊するリスクが低減される。
組み込みプロジェクトの文脈では、この視覚的な明確さによりデバッグ時の認知負荷が軽減されます。デバイスが予期しない動作をした場合、図は期待される動作の真実の出所として機能します。
🏗️ 明確性のための構造的ベストプラクティス
視覚的なごちゃごちゃは保守の敵です。蜘蛛の巣のように見える図は、変更が困難になるコードベースを示しています。モデルをクリーンに保つために、これらの構造的ガイドラインに従いましょう。
1. 図ごとの状態数を制限する
厳密な上限は存在しませんが、20個以上の状態を含む図は、リファクタリングの必要があることを示すことが多いです。高い複雑さは、モデルがやりすぎていることを示唆しています。大きなモデルをサブ図または複合状態に分割しましょう。
- 目安:全体像を把握するために頻繁にズームアウトしている場合は、図を分割しましょう。
- 戦略:上位レベルを混雑させずに、関連する動作をグループ化するために階層状態を使用しましょう。
2. 一貫した命名規則
命名はラベル付けだけの話ではなく、コミュニケーションの一部です。状態名は行動ではなく、状態を表すべきです。遷移ラベルはイベントを表すべきです。
- 良い例:
アイドル,処理中,アイドル->ボタンが押された->処理中. - 悪い:
プロセス開始,入力待ち,ボタン->開始.
状態名は、安定した状態を表す名詞または名詞句でなければなりません。遷移ラベルは、状態変化のトリガーを表す動詞または動詞句でなければなりません。
3. 跨越する遷移を最小限に抑える
図全体を横断する遷移は結合を生じます。状態Aが状態Zに移行する必要がある場合、それらが遠く離れているなら、共通の中間状態や階層構造を用いてこれを調整できるか検討してください。
- 一般的に、遷移は隣接する状態または論理的に関連する状態を結ぶべきです。
- 線が図のキャンバスを交差する「スパゲッティ接続」を避けてください。
🧩 階層構造による複雑さの管理
システムが拡大するにつれて、平坦な状態機械は管理できなくなります。UMLは階層状態機械をサポートしており、状態が他の状態を含むことを可能にします。これは複雑さをスケーリングするための主要なツールです。
1. 組み合わせ状態(スーパー状態)
組み合わせ状態とは、他の状態を含む状態です。これはコンテナとして機能します。これは動作モードをグループ化するのに役立ちます。
- 使用例: 一つの
運用スーパー状態で、次を含む通常モード,保守モード、および診断モード. - 利点:サブステートすべてに適用される遷移を繰り返し記述せずに定義できます。
2. エントリーアクションとエグジットアクション
ステートに入ったり出たりするときに実行されるアクションは、初期化やクリーンアップに強力なツールです。ただし、隠れた依存関係を避けるために注意深く使用する必要があります。
- エントリーアクション:ステートに入ったら、変数を初期化し、タイマーを開始する、または割り込みを有効化する。
- エグジットアクション:ステートを離れるときに、タイマーを停止し、データを保存する、または割り込みを無効化する。
- 警告:重いロジックをここに配置しないでください。ブロッキングを防ぐために、アクションは軽量に保ってください。
3. 直交領域
一部のシステムでは並行動作を処理する必要があります。直交領域により、ステートが同時に複数の状態に存在できるようになります。これは、ディスプレイコントローラーやネットワークハンドラーのような独立したサブシステムに頻繁に使用されます。
- 視覚的表現:状態ボックスをセクションに分ける点線で表現される。
- 実装:コード構造は並行実行をサポートしなければならず、通常は別々のタスクや割り込みハンドラーを通じて実現される。
⚡ イベントと遷移の処理
ステートマシンの論理は遷移に存在する。これらは、システムを一つの状態から別の状態へ移行させるトリガーである。
1. イベントフィルタリング
すべてのイベントがすべてのステートで遷移を引き起こす必要はない。明確なガードを定義してフローを制御する。これにより、システムが処理できないイベントに反応するのを防ぐ。
- ガード条件:遷移が発生するためには、真でなければならないブール式。
- 例:
ButtonPressed[Level == 5].
2. イベントストームの回避
あまりにも多くのイベントは曖昧さを生む。ステートが20個の異なるイベントをリッスンしていると、「ゴッドステート」となる。イベントの表面積を管理可能な範囲に保つこと。
- 可能な限り関連するイベントを複合イベントにグループ化する。
- イベントの生成者と消費者を分離するために、集中型のイベントディスパッチャを使用する。
3. 自己遷移
同じ状態に戻る遷移は正当であり、有用である。これにより、システムはモードを変更せずにアクションを実行できる。
- 使用例:エラーのログ記録、カウンターの更新、LEDのトグル。
- 注意:ステートマシンがポーリングされる場合、アクションが無限ループを引き起こさないことを確認する。
🔄 歴史状態:コンテキストの保持
時折、システムは複合状態を離れる前の位置を記憶する必要がある。歴史状態はこの問題を解決する。
1. 深さの浅い歴史
システムが複合状態の最後にアクティブだったサブ状態に戻るべきであることを示す。サブ状態の履歴は記憶しない。
2. 深い歴史
システムが全体の階層内で最後にアクティブだった状態に戻るべきであることを示す。これは複数のレベルにわたる複雑なワークフローに有用である。
- シナリオ: デバイスが
設定状態に入り、次にネットワークサブ状態に入る。中断され再開された場合、ネットワークに戻るべきであり、単に設定. - 実装: 非揮発性メモリまたはRAMに状態IDを保存する必要がある。
📊 比較:良い習慣 vs. 悪い習慣
これらの概念を定着させるために、以下のシナリオを直接比較する。
| 側面 | ❌ パタンの反例 | ✅ 最良の実践 |
|---|---|---|
| 状態の命名 | TurnOnLED() |
LED_Active |
| 遷移ロジック | 遷移ラベル内のロジック | アクション/効果セクション内のロジック |
| 図のサイズ | すべてのロジックを1つの図にまとめる | 階層状態を使用する |
| イベント処理 | 1つの状態がすべてのイベントを処理する | ガードを使用してイベントをフィルタリングする |
| コードの結合度 | ロジック内にハードコードされた状態ID | 状態IDには列挙型を使用する |
| ドキュメント | 変更後に図が古くなる | CI/CDパイプラインと統合する |
🔗 図を実装にリンクする
設計とコードの間にはバグが隠れがちなギャップがあります。状態機械図と生成されたコードまたは手動で書かれたコードの整合性を確保することは、重要な最良の実践です。
1. 命名の一貫性
図で使用される識別子は、コード内の識別子に直接対応しなければなりません。状態がモデルで「Boot」と名付けられている場合、C/C++の列挙型は「BOOT.
- 手動でのマッピングエラーを減らすために、自動コード生成ツールを使用する。
- 手動でコードを書く場合、リナーやツールを使って厳格な命名規則を強制する。
2. 追跡可能性マトリクス
図の要素が特定のコード関数やファイルにリンクするドキュメントまたはスプレッドシートを維持する。これは安全が重要な認証(例:ISO 26262、DO-178C)にとって不可欠である。
- 状態ID: は以下にマップされる:
switch(state)case. - 遷移: 関数呼び出しまたは論理分岐にマップされる。
- ガード: 検証関数にマップされる。
3. コード生成戦略
コード生成を使用する際は、ツールがクリーンで読みやすいコードを出力するようにする。手動でのデバッグが困難な生成コードを避ける。
- 生成されたコードに、図の状態IDを参照するコメントが含まれていることを確認する。
- コードレビューの過程で生成コードを確認し、アーキテクチャの意図と一致していることを確認する。
🧪 テストと検証
状態機械図は仕様である。テストケースではない。しかし、テスト戦略を導く。
1. 状態カバレッジ
テスト中にすべての状態が少なくとも1回訪問されることを確認する。これはカバレッジツールで追跡可能である。
- 到達不可能な状態がないか確認する。
- すべてのエントリ/エグジットアクションが正しく発火することを検証する。
2. 遷移カバレッジ
定義されたすべての遷移をテストする。これは、特定のソース状態にいるときに特定のイベントを発火させることを含む。
- 高負荷下での遷移の検証にストレステストを使用する。
- 無効な遷移が無視されるか、適切に処理されること(デフォルト動作)を検証する。
3. 故障注入
システムが問題を起こしたときにどう反応するかをテストする。イベントが間違った状態に到着した場合はどうなるか?
- 以下を実装する:
ErrorまたはUnknownState状態を実装して、予期しない遷移をキャッチする。 - エラーをログに記録して、事後分析を支援する。
🛠️ 一般的な落とし穴とその解決策
経験豊富なエンジニアでさえミスをする。ここでは一般的な問題とその解決方法を紹介する。
1. 「神状態」問題
単一の状態にあまりにも多くの論理が含まれる場合に発生し、しばしば未定義の振る舞いをすべて受け持つ役割を果たす。
- 解決策: 論理を複数の特定の状態に分解する。
- 解決策: エラー用のフォールバック状態を使用するが、メインの論理は明確に分離する。
2. 歴史状態の過剰使用
歴史状態は、新規エンジニアにとってフローを追うことを難しくする。隠れた状態を導入する。
- 解決策: 歴史状態は必要最小限のとき(例:永続セッション)にのみ使用する。
- 解決策: 歴史状態の使用を、モデルノートに明確に記録する。
3. ハードウェアへの過度な依存
状態機械はしばしばハードウェアレジスタに直接アクセスするため、PC上でテストすることが難しくなる。
- 解決策: 状態機械とハードウェアの間にハードウェア抽象層(HAL)を使用する。
- 解決策: 状態機械は物理ピンではなく、論理的なサービスとやり取りすべきである。
📈 時間の経過に伴う図の維持
図は生きている文書である。コードとともに進化しなければならない。
- バージョン管理: 図をソースコードと同じリポジトリに保存する。標準的なバージョン管理システムを使用する。
- リファクタリング: コードのリファクタリングを行う際は、図をすぐに更新する。図をレガシードキュメントとして扱わない。
- 視覚スタイル: プロジェクト全体で視覚スタイルを一貫させる。同じ色、フォント、レイアウトルールを使用する。
🎯 設計の厳密さについての結論
信頼性の高い組み込みソフトウェアを構築するには、規律が求められます。状態機械図は、複雑さを管理するために必要な構造を提供します。命名、階層構造、遷移論理に関するベストプラクティスを守ることで、開発・テスト・保守がより簡単なシステムを構築できます。
明確なモデルに投資した努力は、デバッグフェーズで大きな成果をもたらします。適切にドキュメント化された状態機械は、コードダンプを通じて論理を追跡する時間の短縮に貢献します。これにより、「コードは何かをしているのか?」という問いから、「なぜコードはこうしているのか?」という問いに焦点が移ります。
図は設計ツールとしてだけでなく、コミュニケーションツールとしても機能することを忘れないでください。ハードウェアエンジニア、ソフトウェア開発者、テスト担当者すべてにメッセージを伝えるものです。明確に、正確に、実装と整合性を持たせた状態を保ちましょう。











