codebase-design について
ソフトウェアモジュールを設計・再構成する開発者向けのスキルです。小さなインターフェースに多くの振る舞いを隠し、明確なシームとテスト容易性を重視します。設計議論で使う用語も統一します。
主な機能
- 01深いモジュールの設計
- 02インターフェースの簡素化
- 03明確なシームの配置
- 04テスト容易性の向上
- 05設計用語の統一
活用シーン
- 01明確な責務を持つモジュールへの再構成
- 02実装前のインターフェース評価
- 03テストしやすいモジュールの設計
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ maddec01/ mux-factory --skill codebase-designインストール後、Claude Code で/codebase-designを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
DEEPENING.mdDESIGN-IT-TWICE.mdagents/openai.yaml詳細
コードベースの設計
深いモジュールを設計します。小さなインターフェースの背後に多くの振る舞いを置き、明確な接合部に配置し、そのインターフェースを通じてテストできるようにします。コードを設計または再構成する際は、どこでもこの用語と原則を使ってください。目指すのは、呼び出し側の活用しやすさ、保守担当者の局所性、そしてすべての人にとってのテストしやすさです。
用語集
次の用語をそのまま使ってください。「コンポーネント」「サービス」「API」「境界」に置き換えないでください。一貫した用語を使うことが重要です。
モジュール:インターフェースと実装を持つものすべてです。規模を問わない意図的な用語です。関数、クラス、パッケージ、複数の階層にまたがるスライスが該当します。 避ける語:ユニット、コンポーネント、サービス。
インターフェース:モジュールを正しく使うために呼び出し側が知っておく必要のあるすべてです。型シグネチャだけでなく、不変条件、順序の制約、エラーの種類、必要な設定、パフォーマンス特性も含まれます。 避ける語:API、シグネチャ(範囲が狭すぎます。どちらも型レベルの表面のみを指します)。
実装:モジュールの内部、つまりそのコード本体です。アダプターとは異なります。小さなアダプターと大きな実装(Postgres リポジトリ)の場合も、大きなアダプターと小さな実装(インメモリのフェイク)の場合もあります。接合部が話題なら「アダプター」を、それ以外の場合は「実装」を使います。
深さ:インターフェースがもたらす活用しやすさです。呼び出し側(またはテスト)が、覚える必要のあるインターフェースの単位ごとに利用できる振る舞いの量を指します。大量の振る舞いが小さなインターフェースの背後にあるモジュールは深く、インターフェースが実装とほぼ同じくらい複雑なモジュールは浅くなります。
接合部 (Michael Feathers):その場所自体を編集せずに振る舞いを変更できる場所です。モジュールのインターフェースが置かれる位置を指します。接合部をどこに置くかは、その背後に何を置くかとは別の設計上の決定です。 避ける語:境界(DDD の境界づけられたコンテキストと意味が重複するため)。
アダプター:接合部でインターフェースを満たす具体的なものです。中身(何が入っているか)ではなく、役割(どの枠を埋めるか)を表します。
活用しやすさ:深さが呼び出し側にもたらすものです。覚えるインターフェースの単位あたりに得られる機能が増えます。1 つの実装で、N か所の呼び出し元と M 件のテストにわたって効果が得られます。
局所性:深さが保守担当者にもたらすものです。変更、バグ、知識、検証が呼び出し側に広がらず、1 か所に集約されます。一度直せば、どこでも直ります。
深いモジュールと浅いモジュール
深いモジュール = 小さなインターフェース + 大量の実装:
┌─────────────────────┐
│ Small Interface │ ← Few methods, simple params
├─────────────────────┤
│ │
│ Deep Implementation│ ← Complex logic hidden
│ │
└─────────────────────┘
浅いモジュール = 大きなインターフェース + わずかな実装(避ける):
┌─────────────────────────────────┐
│ Large Interface │ ← Many methods, complex params
├─────────────────────────────────┤
│ Thin Implementation │ ← Just passes through
└─────────────────────────────────┘
インターフェースを設計するときは、次の点を確認してください。
- メソッドの数を減らせますか?
- パラメーターを簡素化できますか?
- 内部に隠せる複雑さを増やせますか?
原則
- 深さは実装ではなく、インターフェースの特性です。 深いモジュールは、内部で小さく、モック可能で、差し替え可能な部品を組み合わせてもかまいません。それらはインターフェースには含まれません。モジュールには、自身の実装内部にあり、自身のテストで使う内部の接合部と、インターフェースにある外部の接合部の両方を設けられます。
- 削除テスト。 モジュールを削除すると仮定します。複雑さが消えるなら、それは単なる受け渡しでした。複雑さが N 個の呼び出し元に再び現れるなら、そのモジュールには存在する価値がありました。
- インターフェースがテスト対象の範囲です。 呼び出し側とテストは、同じ接合部を通ります。インターフェースの先までテストしたいなら、そのモジュールの形はおそらく適切ではありません。
- アダプターが 1 つなら仮定上の接合部、2 つなら実際の接合部です。 接合部をまたいで実際に変わるものがないなら、接合部を導入しないでください。
テストしやすさを考慮した設計
優れたインターフェースは、自然な形でテストできます。
-
依存関係を受け取り、作成しないでください。
// Testable function processOrder(order, paymentGateway) {} // Hard to test function processOrder(order) { const gateway = new StripeGateway(); } -
結果を返し、副作用を発生させないでください。
// Testable function calculateDiscount(cart): Discount {} // Hard to test function applyDiscount(cart): void { cart.total -= discount; } -
表面積を小さくします。 メソッドが少なければ、必要なテストも少なくなります。パラメーターが少なければ、テストのセットアップも簡単になります。
関係性
- モジュールには、呼び出し側やテストに提示する表面であるインターフェースがちょうど1つあります。
- 深さはモジュールの特性であり、そのインターフェースを基準に測定されます。
- シームとは、モジュールのインターフェースが位置する場所です。
- アダプターはシームに配置され、インターフェースを満たします。
- 深さは、呼び出し側にレバレッジを、保守担当者に局所性をもたらします。
採用しない捉え方
- 実装行数とインターフェース行数の比としての深さ(Ousterhout):実装の水増しを促します。ここでは、レバレッジとしての深さを採用します。
- 「インターフェース」を TypeScript の
interfaceキーワードやクラスのパブリックメソッドと捉えること:範囲が狭すぎます。ここでのインターフェースには、呼び出し側が知る必要のあるあらゆる事実が含まれます。 - 「境界」:DDD の境界づけられたコンテキストと混同されやすい用語です。シームまたはインターフェースと言ってください。
さらに深く掘り下げる
- 依存関係を踏まえてクラスターを深くする方法については、DEEPENING.md を参照してください。依存関係の分類、シームの規律、置き換えによるテストについて説明しています。
- 別のインターフェースを探る方法については、DESIGN-IT-TWICE.md を参照してください。並行するサブエージェントを立ち上げ、インターフェースを大きく異なる方法で複数設計してから、深さ、局所性、シームの配置を比較します。