


イベントで動くステートマシンを、動きを壊さずに作る・直す
ステートマシンのライブラリを作る・直す時に使う。1つのイベントを最後まで処理してから次へ進め、エラーが出ても後始末を飛ばさず、スレッドと子マシンを安全に保つ。先にテストを書いて同期・非同期の両方のエンジンで回し、コード・文書・スキーマをそろえる。
このAIルールを使うには
## ステートマシンの処理を、どのモジュールに置くか決める
コードを変更する前に責任を対応付け、必要な知識を持つコンポーネントに振る舞いを留める。
### 明確な境界を定める
- 状態とイベントの定義、遷移の合成、コールバックのディスパッチ、シグネチャの適合、実行エンジン、レジストリ、統合、シリアライズを分離する。
- ディスパッチやシグネチャの適合などの汎用基盤を、ステートマシンのドメインから独立させる。
- 各モジュール、クラス、関数に、変更する明確な理由を一つだけ持たせる。
- 便宜のために無関係なモジュールを拡張するより、明示的なインターフェースを持つ小さく焦点の定まったモジュールを優先する。
### ロジックを意図的に配置する
- Information Expert(情報エキスパート)を適用する。必要な知識がすでに存在する場所で結果を計算し、別の場所で再計算せず、その結果を返すか通知する。
- Law of Demeter(デメテルの法則)を適用する。協力する要素には親オブジェクトではなく、必要な具体的な値を渡す。
- Interface Segregation(インターフェース分離)を適用する。ヘルパーがオブジェクト全体を必要としない場合は、限定されたフィールドまたはプロトコルを受け取る。
- リファクタリング時も公開されている振る舞いを保ち、ロジックを移動する前にその振る舞いを特徴付けるテストを特定する。
## 1つのイベントを最後まで処理してから、次へ進む
イベント処理を明確に異なる二つのレベルでモデル化し、実装時に一つへ統合しない。
### 処理レベルを区別する
- マイクロステップは、選択された一組の遷移を定義された順序でアトミックに実行する。順序は、事前コールバック、退出コールバック、遷移アクション、進入コールバック、事後コールバックとする。
- マクロステップは一つの外部イベントを処理し、マシンが安定構成に達するまでマイクロステップを繰り返す。
- イベントなし遷移が有効でなく、内部キューが空の場合に限り、構成は安定している。
### キューとイベントのセマンティクスを明示する
- 外部から送られたイベントは外部キューに入り、現在のマクロステップが終わるまで待つ。
- 内部で発生したイベントは内部キューに入り、後続の外部イベントより前に、現在のマクロステップ内で実行される。
- 割り当てのない遷移宣言は、ガードが真になった時に発火するイベントなし遷移を表す場合がある。
- 割り当てられた遷移は名前付きイベントを作る。命名規則から別名を導出する場合でも、明示的な識別子を優先する。
- キュー順序、イベントなし遷移の進行、安定構成での終了を、それぞれ独立した振る舞いとしてテストする。
## エラーをイベントとして扱い、後始末のコールバックも飛ばさない
エラーを捕捉する場所と、失敗後も実行するライフサイクルコールバックを定める。
### エラー境界を設定する
- マシン種別ごとに、エラーをイベントとして捕捉する方針を明示する。チャート指向の派生形と基本的な派生形で、意図的に逆の既定値を使う場合がある。
- マイクロステップ全体を一つの捕捉境界として扱わず、アクションブロック単位でエラーを捕捉する。ライフサイクルが保証する場合は、成功時と失敗時の両方で終了処理コールバックを実行する。
- 捕捉した実行エラーを内部イベントとしてディスパッチし、状態チャートが宣言済みの遷移で処理できるようにする。
- 内部の実行エラーイベントを処理している間に生じたエラーを抑止または終了させ、再帰的な失敗ループを防ぐ。
### コールバックの区別を保つ
- 汎用ライフサイクルコールバックを、イベント固有および状態固有のコールバックから分離する。
- エラー名を持つコールバックは自動的に汎用にはならない。規約により呼び出す前に、対応するエラー遷移があることを必須とする。
- エラーイベント処理中に遷移アクションがローカルのエラー捕捉を無効にする場合、その規則を、常に自身のエラーハンドラーを使う進入ブロックおよび退出ブロックの規則と区別する。
- 成功、通常の失敗、エラー処理中の失敗、終了処理の順序をそれぞれ独立してテストする。
## キューやロックを変える時も、スレッドと子マシンを安全に保つ
キュー、ロック、子マシン管理を変更する際も、スレッド安全性と呼び出しルーティングの保証を維持する。
### 安全な並行処理を維持する
- 一つのマシンインスタンスで同時に遷移を実行できるスレッドは、最大一つとする。
- 複数の送信元を並行して扱う場合は、スレッドセーフな優先度付きキューを使う。
- 並行処理の契約を再設計して実証しない限り、スレッドセーフなキューを通常のリストやスレッドセーフでない両端キューに置き換えない。
- 送信スレッドとバリアを連携させ、実際の競合状態に対するストレステストを行う。
- ブロッキングスレッドのテストでは、後片付け中にスレッドが残る可能性のある固定スリープではなく、明示的なタイムアウトを持つ割り込み可能なイベント待機を使う。
### 呼び出された子マシンを正確に管理する
- 子の呼び出しは、終了済みかつキャンセル済みの場合に限り取り除く。
- 終了済みでもキャンセルされていない子は、その所有状態がアクティブである間、ルーティング可能な状態に保つ。
- 処理ループ内で子の構築がブロックする場合は、ループを続行する前に、早期のライフサイクルリスナーを通じて子の参照を取得する。
- ライフサイクル制御を迂回せず、呼び出しマネージャーと子ハンドラーを通じて、子を対象とするイベントをルーティングする。
- キャンセル、自然終了、保持されたルーティング、遅延イベントの配信を、それぞれ別のケースとしてテストする。
## 決められた開発環境でテストを回し、分岐カバレッジを測る
インストール、テスト、品質確認、文書化は、プロジェクトで固定された環境とタスクランナーを通じて実行する。
### テストを安全に実行する
- テスト一式全体に明示的なタイムアウトを設定し、残留スレッドや無限ループを明らかにする。
- 意図的な待機や代替のタイムアウトを含むテストでは、テスト一式全体でサポートされている並列実行を使う。
- パスを限定した実行ではモジュールまたは文書のdoctestが対象外になる場合、プロジェクトルートから正本のテスト一式を実行する。
- 確立された基準時間を超える実行は、原因を調べずに繰り返すのではなく調査する。
- 全体のデバッグログによって性能測定が変わる場合、詳細なデバッグログは対象を絞った調査でのみ有効にする。
- 大量の出力は一度だけ取得し、同じ高コストのテスト一式を繰り返し実行するのではなく、保存した結果を分析する。
### 意図したカバレッジを測定する
- 必要な場合はカバレッジ収集を明示的に有効にし、影響を受けるモジュールについて、未実行行を示すレポートまたは機械可読レポートを生成する。レポート形式を選ぶだけでは収集が有効にならない場合がある。
- コミット前に、行カバレッジだけでなく分岐カバレッジも確認する。
## 先に失敗するテストを書き、同期・非同期の両方のエンジンで回す
コードを書いた後の後片付けではなく、実装要件としてテストを扱う。
### テスト駆動のループに従う
1. 計画時に、新しい分岐、境界条件、エラー経路、並行処理のリスクを特定する。
2. 欠けている、または誤った振る舞いに対して失敗する、焦点を絞ったテストを追加する。
3. そのテストを通す最小の振る舞い変更を実装する。
4. 対象テスト、影響を受けるモジュールのカバレッジ、必要なより広い範囲の確認を実行する。
5. 振る舞いとカバレッジを保護した後にだけリファクタリングする。
### テスト基盤を再利用する
- 共有のパラメーター化ランナーを使い、同じ状態チャートを同期エンジンと非同期エンジンの両方で実行する。
- テストクラスを重複させたり何もしないリスナーを追加したりせず、ランナーにエンジンの起動とイベント配信の差を抽象化させる。
- 一時パス、環境の変更、可変状態にはフィクスチャを使う。パスをハードコードせず、可変のグローバル状態に依存しない。
- 到達可能な防御的分岐は必ずテストで網羅する。不可能であることが確立された場合に限り、分岐をカバレッジ対象から外す。
- 標準のコミット確認で、必須の分岐カバレッジ基準を継続的に適用する。
## コードの点検を回し、パーサーとスキーマをそろえる
実装、例、生成される契約、開発者向け文書に、一貫した品質体系を適用する。
### コード規約を適用する
- プロジェクトの正本ツールチェーンを通じて、フォーマッター、リンター、インポートソーター、型チェッカー、テストを実行する。
- トップレベルのインポートは並び替えた状態を優先する。遅延インポートは循環依存を断つ必要がある場合に限って使う。
- プロジェクトの命名規則とdocstring規則に従い、明白な名前や型を繰り返すのではなく、表面から見えないセマンティクスを文書化する。
- 文書生成器が要求する場合は、属性の直下に属性docstringを置く。インラインコメントは明白でないロジックにだけ使う。
### 文書と宣言形式をテストする
- 実用的な場合は、文書の例を実行可能なdoctestにする。
- 実際の並行処理が必要な例は単体テストに置き、文書から参照する。
- 宣言語彙を変更する際は、パーサー、実行時の振る舞い、公開スキーマの同期を保つ。
- パーサーが受理するキーの集合を正本とし、対応するスキーマのプロパティと照合するテストを行う。
- 構造化されたすべての例とフィクスチャをスキーマに照らして検証し、パーサーとスキーマのずれによってテスト一式が失敗するようにする。
## コミットフックを飛ばさず、秘密の情報も入れずに変更を仕上げる
リポジトリのコントリビューション手順を使い、追跡可能性、生成物、セキュリティ境界を保つ。
### 品質ゲートを完了する
- リポジトリで指定された開発ブランチを対象とし、コミットメッセージの規約に従う。
- プロジェクトが推奨する場合は署名付きコミットを使う。
- コミットフックを決して迂回せず、必須の確認を無効にしない。
- フックがファイルを再生成または変更した場合は、その結果を確認してステージし、すべてのフックが通るまで再度コミットする。
- 最終差分を確認し、実装、テスト、文書、生成された契約が一致していることを確かめる。
### 信頼境界を守る
- シークレット、認証情報、環境ファイルを決してコミットしない。
- システム境界で信頼できないデータを検証し、内部の不変条件を明示する。
- 変更を通すためだけに、キューの安全性、エラーループ防止、スキーマ検証、カバレッジ適用を弱めない。
- 実行できなかった必須確認を報告し、直接の結果がないまま完了を主張しない。
