codebase-design について
コーディングエージェント向けに、深いモジュールの原則を設計やリファクタリングに適用するスキルです。小さなインターフェースの背後に多くの振る舞いを隠し、呼び出し側の活用度と保守時の局所性を高め、同じシームを通じてテストします。
主な機能
- 01小さなインターフェースで深いモジュールを設計
- 02モジュール、インターフェース、実装の区別
- 03シームとアダプターの慎重な選定
- 04削除テストによるモジュール評価
- 05テストしやすいインターフェース設計
- 06複数のインターフェース案の比較
活用シーン
- 01明確なシームを軸にしたコード再構成
- 02呼び出し側に見える複雑さの削減
- 03単なる受け渡しモジュールの見極め
- 04インターフェース設計によるテスト容易性の改善
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ leandrocfe/ skills --skill codebase-designインストール後、Claude Code で/codebase-designを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
DEEPENING.mdDESIGN-IT-TWICE.mdagents/openai.yaml詳細
コードベースの設計
**深いモジュール(deep modules)**を設計します。小さなインターフェースの背後に多くの振る舞いを置き、明確なシームに配置し、そのインターフェースを通じてテストできるようにします。コードを設計または再構成するときは、常にこの用語と原則を使ってください。目標は、呼び出し側にとってのレバレッジ、メンテナーにとっての局所性、そしてすべての人にとってのテスト容易性です。
用語集
以下の用語をそのまま使ってください。「component」「service」「API」に置き換えないでください。一貫した用語を使うことが最優先です。
Module — インターフェースと実装を持つものなら何でも指します。規模を意図的に問いません。関数、クラス、パッケージ、あるいはレイヤーをまたぐスライスも含みます。避ける用語: unit、component、service。
Interface — モジュールを正しく使うために呼び出し側が把握する必要のあるすべてを指します。型シグネチャだけでなく、不変条件、順序の制約、エラーの種類、必要な設定、パフォーマンス特性も含みます。避ける用語: API、signature(範囲が狭すぎます。型の表面だけを指します)。
Implementation — モジュール内部、つまりコード本体を指します。Adapterとは異なります。小さなadapterと大きなimplementationを持つもの(Postgresリポジトリ)もあれば、大きなadapterと小さなimplementationを持つもの(インメモリのfake)もあります。シームが主題の場合は「adapter」を、それ以外の場合は「implementation」を使ってください。
Depth — インターフェースにおけるレバレッジ、つまり呼び出し側(またはテスト)が、把握する必要のあるインターフェースの単位ごとに実行できる振る舞いの量を指します。多くの振る舞いが小さなインターフェースの背後にあるモジュールはdeepであり、インターフェースが実装とほぼ同じくらい複雑なモジュールはshallowです。
Seam (Michael Feathers) — その箇所を編集せずに振る舞いを変更できる場所、つまりモジュールのインターフェースが存在する位置を指します。シームをどこに置くかは、それを越えた先に何を置くかとは別個の設計判断です。避ける用語: boundary(DDDのbounded contextと意味が重複します)。
Adapter — シームにおいてインターフェースを満たす具体的なものを指します。中身(何で構成されているか)ではなく、役割(どのスロットを埋めるか)を表します。
Leverage — depthによって呼び出し側が得るもの、つまり把握するインターフェースの単位ごとに得られる能力の向上を指します。一つの実装がN個の呼び出し箇所とM個のテストで効果を発揮します。
Locality — depthによってメンテナーが得るもの、つまり変更、バグ、知識、検証が呼び出し側に分散せず、一箇所に集約されることを指します。一度直せば、どこでも直ります。
深いモジュールと浅いモジュール
深いモジュール = 小さなインターフェース + 大きな実装:
┌─────────────────────┐
│ Small Interface │ ← Poucos métodos, parâmetros simples
├─────────────────────┤
│ │
│ Deep Implementation│ ← Lógica complexa escondida
│ │
└─────────────────────┘
浅いモジュール = 大きなインターフェース + 小さな実装(避けてください):
┌─────────────────────────────────┐
│ Large Interface │ ← Muitos métodos, parâmetros complexos
├─────────────────────────────────┤
│ Thin Implementation │ ← Apenas repassa
└─────────────────────────────────┘
インターフェースを設計するときは、次の点を問いかけてください。
- メソッドの数を減らせますか?
- パラメーターを簡素化できますか?
- より多くの複雑さを内部に隠せますか?
原則
- Depthは実装ではなく、インターフェースの特性です。 深いモジュールは、内部では小さく、モック可能で、交換可能な要素から構成できます。それらは単にインターフェースの一部ではありません。モジュールは、自身のテストで使う実装内のinternal seams(実装内に限定されたシーム)と、インターフェースにあるexternal seamの両方を持つことができます。
- 削除テスト。 モジュールを削除すると想像してください。複雑さがなくなるなら、それは単なる受け渡し役でした。複雑さがN個の呼び出し側に再び現れるなら、そのモジュールは役割を果たしていました。
- インターフェースはテスト対象の境界です。 呼び出し側とテストは、同じシームを通ります。インターフェースを越えてテストしたいなら、そのモジュールはおそらく適切な形ではありません。
- Adapterが1つなら、シームは仮定にすぎません。Adapterが2つなら、シームは実在します。 そのシームを介して実際に何かが変わるのでない限り、シームを導入しないでください。
テスト容易性を考慮した設計
優れたインターフェースなら、テストも自然に行えます。
-
依存関係を受け取り、自分で作成しません。
// Testável function processOrder(order, paymentGateway) {} // Difícil de testar function processOrder(order) { const gateway = new StripeGateway(); } -
結果を返し、副作用を発生させません。
// Testável function calculateDiscount(cart): Discount {} // Difícil de testar function applyDiscount(cart): void { cart.total -= discount; } -
小さなサーフェス。 メソッドが少ないほど、必要なテストも少なくなります。パラメーターが少ないほど、テストのセットアップも簡単になります。
関係性
- 1 つの Module には、Interface がちょうど 1 つあります(呼び出し元とテストに提示するサーフェスです)。
- Depth は Module の特性であり、その Interface を基準に測定されます。
- Seam は、Module の Interface が存在する場所です。
- Adapter は Seam に配置され、Interface を満たします。
- Depth は、呼び出し元に Leverage を、メンテナーに Locality をもたらします。
採用しない捉え方
- Depth を実装の行数とインターフェースの行数の比率とする考え方(Ousterhout):実装を膨らませるほど有利になります。代わりに、Depth を leverage として捉えます。
- 「Interface」を TypeScript の
interfaceキーワードやクラスのパブリックメソッドとする考え方:範囲が狭すぎます。ここでいうインターフェースには、呼び出し元が知る必要のある事実がすべて含まれます。 - 「Boundary」:DDD の bounded context と意味が重複します。seam または interface と呼んでください。
さらに掘り下げる
- 依存関係を考慮してクラスターを深くする — DEEPENING.md を参照してください。依存関係のカテゴリ、seam の規律、テストでの replace-don't-layer 戦略について説明しています。
- 別のインターフェース案を検討する — DESIGN-IT-TWICE.md を参照してください。サブエージェントを並行して実行し、根本的に異なる複数の方法でインターフェースを設計してから、depth、locality、seam の配置の観点で比較します。