tracer-codebase-design について
tracer-codebase-designは、コードの設計や再構成に使う開発スキルです。小さなインターフェースの背後に多くの振る舞いを持つ深いモジュールを目指し、保守性とテスト容易性を高める原則を示します。
主な機能
- 01小さなインターフェースで深いモジュールを設計
- 02インターフェース、実装、アダプターを区別
- 03シームとモジュールの深さを評価
- 04テストしやすいインターフェースを設計
- 05複数のインターフェース案を比較
活用シーン
- 01複雑さを集約するコードの再構成
- 02呼び出し側のインターフェースを簡素化
- 03動作が変わり得る箇所へのシーム配置
- 04モジュールのインターフェースを通じたテスト改善
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ scottwater/ skills --skill tracer-codebase-designインストール後、Claude Code で/tracer-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 を参照してください。並行して動くサブエージェントに、まったく異なる複数の方法でインターフェースを設計させ、深さ、局所性、シームの配置を基準に比較します。