


コンポーネントの多い大きなコードベースを、読みやすく壊さず直せる形に保つ
コンポーネントが多い大きなコードベースで、AIにコードを書かせる時に使う。コードの書き方、ファイルと変更の大きさ、コードレビューで見る所、テスト、APIの名前の付け方、ビルドの進め方の決まりを渡し、変更を小さく読みやすく保って、ほかの所を壊さないようにする。
このAIルールを使うには
## true/falseだけの引数を避け、呼び出しを読めば分かるコードにする
巧妙さよりも呼び出し側での分かりやすさを優先する。
- 変数は位置引数として渡すのではなく、文字列フォーマットのプレースホルダーに直接インライン化する。
- 言語が許す場合、単純なクロージャよりもメソッド参照を優先する。
- 読み手に意味を推測させてしまう不透明な位置引数(裸のブール値、null/none、数値リテラルなど、例: `foo(false)`)は避ける。enum・名前付きメソッド・newtypeなど、それ自体で意味が分かるAPIの形を優先する。
- 自己説明的な形にできず位置リテラルを残さざるを得ない場合は、呼び出し先のパラメータ名と完全に一致するインラインコメント `/*param_name*/` を付ける。文字列/文字リテラルは、コメントが本当に分かりやすさを高める場合を除き注釈しない。
- 分岐は網羅的にする(catch-allのワイルドカードアームを使わない)ことで、新しく追加されたケースがコンパイル時に表面化するようにする。
- 新しく追加した抽象化(trait/インターフェース)には、その役割と実装者がどう使うべきかを説明するドキュメントコメントを付ける。
- ファイルローカルな慣習に従う。明確な可読性・機能上の向上がないまま同等な書き方の間でコードを行ったり来たりさせない。また、省略記法を満たすためだけに型注釈を追加しない。
- 一度しか参照されない小さなヘルパー関数は作らない。
## 中心のコンポーネントを太らせず、ファイルと変更を小さく分ける
無関係な変更同士が衝突しなくなるよう、単位を小さく保つ。
### モジュールサイズ
- 既存モジュールを肥大化させるより、新しいモジュールを追加することを優先する。テストコードを除いておよそ500行未満のモジュールを目指し、ファイルが約800行に近づいたら、そうしない明確で文書化された理由がない限り、新機能は新しいモジュールに置く。
- すでに無関係な編集を引き寄せている、アクセス頻度の高い中心的なオーケストレーションファイルには、これを最も厳格に適用する。そうしたファイルは単独のヘルパーを蓄積させず、オーケストレーションに専念させる。
- 大きなモジュールからコードを抽出する際は、そのテストと型/モジュールのドキュメントも新しい実装と一緒に移動させ、不変条件がそれを所有するコードの近くに留まるようにする。
### 最大のコンポーネントを肥大化させない
- 最大あるいは最も中心的なコンポーネントは新しいコードにとって最も抵抗の少ない道であり、まさにそれゆえに肥大化し続ける。そこに追加することに抗う。まず、既存の別コンポーネントの方が適した置き場所ではないか、あるいは新しいコンポーネントを導入すべき時期ではないかを問い、必要に応じてリファクタリングする。レビューでは、中心的なコンポーネントを不必要に肥大化させる変更に対して押し戻す。
### 変更サイズ
- 機械的な変更でない限り、変更行数の合計はおよそ800行未満に、複雑なロジックの場合はおよそ500行未満に抑える。
- 変更がそれより大きくなる場合は、レビュー可能な段階に分割し、実際の差分・依存関係・影響を受ける呼び出し箇所に基づいて分割したうえで、最小のまとまった段階から先にマージする。
## コードレビューで、互換を壊す変更とコンテキストの増えすぎを見つける
後になって発見するとコストの高いリスクをレビューで捉える。
### 公開範囲を小さく保つ
各コンポーネントの公開/エクスポートされたAPIはできるだけ小さく保つ。内部を非公開にし、明示的にエクスポートされた公開部分を持つ形を優先し、テスト専用のヘルパーが本番コードの中で増殖しないようにする。
### モデル/推論のコンテキストを守る
システムが各推論リクエストごとにモデルへ送るコンテキストを保持している場合、それを守る。
- コンテキストは段階的に積み上げる。履歴を書き換えない。
- キャッシュを無効化するような頻繁なコンテキスト変更は避ける。
- 注入するものはすべて、サイズに上限があり、かつ厳格な上限を持たなければならない。無制限な項目を許さず、単一項目が固定のトークン上限を超えることも許さない。
- 単体で大きなサイズ閾値を超えうる新しい項目には、追加の手動レビューが必要であるという印を付ける。
- 注入するすべての断片は、その場しのぎの挿入ではなく、単一の型付き抽象化を経由させる。
### 破壊的変更を検出する
外部連携面における破壊的変更を積極的に探す:公開API、(実験的なものも含む)イベントペイロード、コマンドラインパラメータ、設定の読み込み、既存セッションや永続化された状態を再開する機能。
## 統合テストとスナップショットテストで、実際の動きを確かめる
振る舞いをテストし、テストを誠実に保ち、本番コードの外に置く。
- 中核となる振る舞いの変更については、狭い範囲の単体テストよりも、実際のインスタンスを動かす統合テストを優先する。その振る舞いへの変更には必ず統合テストを追加しなければならない。カバーすべき振る舞いを列挙する。
- 単体テストは実装ファイルにインラインで書くのではなく、専用の隣接するテストファイルに置き、そのファイルには内容が分かる名前を付ける。テスト専用の関数を本番モジュールに追加しない。また、この規約に従わせることだけを目的として既存のインラインテストモジュールを書き換えない。
- ユーザーに見える描画出力への変更には、必ずスナップショットによるカバレッジを含めること。スナップショットを追加または更新し、その差分を変更の一部としてレビューすることで、UIへの影響が可視化された状態を保つ。
- フィールドを1つずつ比較するのではなく、明確な差分を出すアサーションライブラリを使って、オブジェクト全体を深い等価性で検証する。
- テストの中でグローバルなプロセス状態(例えば環境変数)を変更しない。依存関係やフラグは上位から渡す。
- 静的に定義された定数をテストしない。また、削除されたロジックに対するネガティブテストを追加しない。
- 新しいセットアップを書く前に既存のテストヘルパーを確認し、テストが簡潔で読みやすい状態を保つ。
## APIの名前とフィールドの書き方を、どこでも同じにそろえる
ネットワーク越しの、あるいは言語をまたぐAPIを進化させる際、通信境界での一貫性がサイレントな破壊を防ぐ。
- 現行の開発は現在のAPIバージョンに集中させる。非推奨バージョンに新しい機能面を追加しない。
- ペイロードは役割に応じて一貫した命名にする(リクエストパラメータ用のサフィックス、レスポンス用のサフィックス、通知用のサフィックス)。メソッドは、リソースの単数/複数の扱いを一貫させたうえで、リソース名に続けてアクション名を付けた形で公開する。
- フィールドとenum値については、通信上のケーシング規則を1つに固定し、あらゆる場所に適用する。上書きするのは明示的な互換性の必要がある場合のみとする。シリアライザと型生成のアノテーションは両者が一致するよう揃えておき、一方が明示的なリネームを使う場合は、もう一方もそれに合わせる。
- 判別可能なユニオン(discriminated union)については、すべてのシリアライザで明示的なタグ付けを使う。
- 境界では単純な文字列IDを優先し、内部で変換する。タイムスタンプは整数を使い、`*_at` という一貫した命名にする。
- リスト系エンドポイントでは、デフォルトでカーソル方式のページネーションを実装する(入力はカーソルとlimit、出力はデータ配列と次のカーソル)。
- 実験的な機能面は明示的なマーカーの背後に隠す。APIの形が変わるたびに、ドキュメント・サンプル・スキーマのフィクスチャを再生成する。
## フォーマッタと絞ったテストを先に回し、サンドボックスのガードは触らない
ツールチェーンと、すでに存在する制約を尊重する。
### 環境とサンドボックスのガード
- サンドボックスや環境の機能フラグを管理するコードは、明確な意図なく追加・変更しない。これらのガードは、コードやテストが特定のランタイム上の制約(例えば、ネットワークアクセスのような機能が利用できない場合に早期終了するテストなど)を前提に書かれているために存在する。ガードを黙って取り除いたり変更したりすると、その前提が壊れる。
### ビルドとツールチェーンの健全性
- コードの変更を終えたら自動でフォーマッタを実行する。フォーマットの実行許可をいちいち求めない。
- まず、変更したコンポーネントに絞った、最も範囲の狭い関連テストを実行する。共有コードや中心的なコードが変更された場合にのみ、フルスイートへ範囲を広げる。長時間かかるフルスイートの実行を始める前には確認を取る。
- スキーマ・ロックファイル・生成された型など、生成物の元になる入力を変更した場合は、同じ変更の中でそれらを再生成する。ずれはCIで検証される。
- ビルドツールがコンパイル時アクセスのためにソースファイルの宣言を必要とする場合、そのような読み込みを追加するたびにその宣言を更新する。
- ビルドやテストのコマンドが遅くても辛抱強く待つ。想定されるロックの競合によって遅くなることがある。プロセスIDを指定して強制終了しない。
### プラットフォームサポート
- ある機能が明示的にOS固有のものでない限り、テストと機能は公式にサポートされているすべてのオペレーティングシステムに対応しなければならない。システムがコンポーネントを異なるOS上で動かせるようにしている場合は、新しいテストがそれらのクロスOS構成をきちんと動かすようにする。
