コードベース設計
深いモジュールを設計します。小さなインターフェースの背後に多くの振る舞いを持ち、呼び出し元とテストが交わるシームに配置します。呼び出し側にはレバレッジを、保守側にはローカリティを、そしてリファクタリングに耐えるテストを目指します。
ルール
- 深さはインターフェースの特性であり、実装の特性ではありません。 深いモジュールは、小さく交換可能な部品で構成される可能性があります。それらはモジュールのインターフェースの一部ではありません。モジュールは、自身のテスト用の内部シームと、インターフェース上の外部シームの両方を持つことができます。
- 削除テストを実行します。 モジュールを削除したと想像してください。複雑さが消え去るなら、それは単なるパススルーです。複雑さが呼び出し元全体に再出現するなら、そのモジュールは存在価値があります。
- インターフェースがテスト面です。 呼び出し元とテストは同じシームを越えます。インターフェースを越えてテストする必要がある場合、モジュールの形状がおそらく間違っています。
- アダプターが1つなら仮想的なシームであり、2つなら本物になります。 実際に何かが変化する場所(通常は本番とテスト)にのみシームを追加します。
- インターフェースを縮小します。 より少ないメソッド、よりシンプルなパラメータ、内部に隠されたより多くの複雑さ。
- 依存関係を作成するのではなく、受け入れます。 関数内で支払いゲートウェイを構築するのではなく、渡すようにします。
- 設計が許す場合は、副作用を生み出す代わりに結果を返します。
語彙
これらの用語を正確に使用してください。「コンポーネント」「サービス」「API」「境界」に置き換えないでください。
- モジュール(Module): インターフェースと実装を持つあらゆるもの。任意の規模(関数、クラス、パッケージ、または階層を横断するスライス)を含みます。
- インターフェース(Interface): 呼び出し元がモジュールを正しく使用するために知っておくべきすべてのこと:型、不変条件、順序、エラーモード、必要な設定、パフォーマンス。単なるシグネチャだけではありません。
- 実装(Implementation): モジュール内部のコード。小さなアダプターが大きな実装を持つことも(Postgresリポジトリ)、大きなアダプターが小さな実装を持つことも(インメモリのフェイク)あります。
- 深さ(Depth): インターフェースにおけるレバレッジ。呼び出し元やテストが学習しなければならないインターフェースの単位あたりに実行できる振る舞いのこと。モジュールは、小さなインターフェースの背後に多くの振る舞いがある場合に深く、インターフェースが実装とほぼ同じくらい複雑な場合に浅くなります。
- シーム(Seam)(Michael Feathers): その場所を編集せずに振る舞いを変更できる場所。モジュールのインターフェースが存在する場所。シームの選択はそれ自体が独立した判断です。
- アダプター(Adapter): シームにおいてインターフェースを満たす具体的なもの。役割を命名するものであり、中身を命名するものではありません。
- レバレッジ(Leverage): 呼び出し元が深さから得るもの。1つの実装が多数の呼び出しサイトとテストにわたって見返りをもたらします。
- ローカリティ(Locality): 保守担当者が深さから得るもの。変更、バグ、知識、検証が1か所に集中します。
棄却されたフレーミング:深さを実装の行数とインターフェースの行数の比率とすること(パディングを促進するため)。「インターフェース」を単なる型キーワードやパブリックメソッドとすること(狭すぎるため)。「境界」という用語(DDDの境界づけられたコンテキストと衝突するため)。
プロジェクトがCONTEXT.mdの用語集を保持している場合は、そのドメイン用語をこれらと併用してください。
さらに深く
- 依存関係の種類ごとに浅いモジュールのクラスターを深くするには、DEEPENING.md を読んでください。
- 根本的に異なる複数のインターフェースを比較するには、DESIGN-IT-TWICE.md を読んでください。このファイルは3つ以上のサブエージェントを起動するため、ユーザーが要求した場合にのみ使用してください。