codebase-design について
codebase-designは、まとまりのある振る舞いを小さく安定した呼び出し契約の背後に置くモジュール設計スキルです。開発者が責務や呼び出し側の負担を検討し、契約を設計・テストする際に役立ちます。
主な機能
- 01深いモジュールの設計指針
- 02呼び出し契約の負担分析
- 03シームとアダプターの原則
- 04契約を設計・テストする手順
- 05関連するアーキテクチャスキルへの案内
活用シーン
- 01関連するモジュール動作の統合
- 02新規または変更する契約の設計
- 03モジュールが判断を隠せているかの評価
- 04安全なアーキテクチャ移行の計画
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ citypaul/ .dotfiles --skill codebase-designインストール後、Claude Code で/codebase-designを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
LICENSEagents/openai.yamlreferences/deepening.mddesign-it-twice.mdsource-notes.md詳細
コードベースの設計
深いモジュールを設計します。関連する実質的な振る舞いと設計上の決定を、呼び出し側向けの小さく安定した契約の背後に隠します。呼び出し側へのレバレッジと、保守担当者にとっての局所性を高めつつ、何でも抱え込む巨大なモジュールは作らないようにします。
論理的な責務と契約の形には、このスキルを使用してください。物理的なパス、パッケージ、エクスポート、依存関係の方向、適用方法、フォルダーの移行には structure-codebase を使用してください。選択した目標が、より深い契約ではなく、処理経路全体の仕組みに関する根拠に基づいた正味の削減を主張することである場合は、reduce-system-complexity を使用してください。ライブラリ、ツール、アプリケーション、サービス、フレームワーク、プラットフォームについて、重大な影響を伴う未解決の選択肢がある場合は、evaluate-existing-solutions を使用してください。
improve-codebase-architecture が未選択の監査中にこのスキルを読み込んだ場合は、語彙、原則、根拠の確認方法、薄い境界の保護策のみを使用してください。ユーザーが候補を選択するまでは、契約設計のワークフローを実行したり、具体的なインターフェースを提案したりしないでください。
重大な設計を提案する前に、関連するリファレンスを読んでください。
- 既存のモジュールの統合、依存関係の分類、安全な深化の移行計画を行う場合は、
references/deepening.mdを読んでください。 - 新しい契約や変更後の契約を元に戻すコストが高い場合、または妥当な形が複数ある場合は、
references/design-it-twice.mdを読んでください。 - 出典を説明したり、このアダプテーションとその出典を比較したりする場合は、
references/source-notes.mdを読んでください。
用語
| 用語 | 意味 |
|---|---|
| モジュール | 実装と、1つ以上の役割に応じた呼び出し側向け契約を持つ、まとまりのある単位です。関数、オブジェクト、パッケージ、機能などが該当します。規模だけでモジュールと判断することはできません。 |
| インターフェース/公開契約 | モジュールを正しく使うために呼び出し側が知っておく必要のあるすべての事項です。操作、型、不変条件、エラー、順序、設定、ライフサイクル、副作用、関連する性能特性が含まれます。TypeScript の interface や型シグネチャより広い概念です。 |
| 実装 | 呼び出し側向けの契約の背後に隠されている決定と振る舞いです。プライベート関数は、小さく数が多くても、公開モジュールになるわけではありません。 |
| 深さ | 呼び出し側が学ぶ必要のある契約の負担に対して、得られる一貫した機能と意思決定の量です。コード行数で深さを測らないでください。 |
| レバレッジ | 深さがもたらす呼び出し側のメリットです。1つの契約を学ぶだけで、さまざまなシナリオに有用な振る舞いを一貫して適用できます。 |
| 局所性 | 深さがもたらす保守担当者のメリットです。関連する知識、変更、バグ、検証が1つの所有者に集まります。 |
| シーム | Michael Feathers によると、その箇所を編集せずに振る舞いを変更できる場所です。すべてのシームには有効化ポイントがあります。すべてのモジュール契約がシームというわけではありません。 |
| アダプター | シームで選択される、具体的な変換器または実装です。ヘキサゴナルアーキテクチャでは、そのスキルにおける駆動側、被駆動側、テストインタラクターの区別を維持してください。 |
これらの用語は意味を明確にするために使い、有用で定着した用語を置き換えるためには使わないでください。API、component、service、signature、boundary、port、bounded context は、それぞれ固有の概念を指す場合、引き続き有効です。
設計原則
コードだけでなく、決定を隠す
呼び出し側がそれぞれポリシー、処理順序、表現、エラー回復、プロバイダーの仕組みを知るべきでない場合は、それらの責務をモジュールの背後に配置してください。プライベートヘルパーを切り出しても、パラメーターや呼び出し順序を通じて同じ知識が漏れたままなら、モジュールは深くなりません。
深さの一貫性を保つ
一貫性のない実装に小さなコントラクトをかぶせても、それは優れた深いモジュールではなく、神モジュールです。意味、インバリアント、所有権、ライフサイクル、または実際の変更軸を共有する場合にのみ、振る舞いをまとめてください。独立して進化、障害、デプロイ、または認可が行われるモジュールは分離したままにします。読者がそのモジュールの所有する判断や責任を言葉にでき、ファイルをまたいで再構成する負担が減るなら、抽出によって境界を設ける価値があります。ヘルパーごとにファイルを作ったり、単純な転送関数を連鎖させたりしても、所有権は確立されません。
振る舞いを保つインライン化テストを適用する
振る舞いを保ったまま、そのモジュールをすべての呼び出し元にインライン化することを想像してください。
- ポリシー、呼び出し順序、エラー処理、または表現に関する知識が呼び出し元に広がるなら、そのモジュールには存在意義があります。
- 中継をなくすことで知識の重複も生じずに呼び出し元が簡潔になるなら、そのモジュールは浅いか、配置が適切ではありません。
これが削除テストの有用な形です。振る舞いそのものを削除することは想像しないでください。
意図的に複雑さを下層へ移す
一般的な呼び出しを簡潔にします。複雑さを実装の内部で引き受けることで、呼び出し元の設定、順序付け、特殊ケース、プロバイダーに関する知識を減らせるなら、そうしてください。呼び出し元がモジュールを安全に使用できる程度に、作用、失敗モード、リソースの所有権、パフォーマンスコストを明示します。
根拠をもって境界を正当化する
置き換え、独立したテスト、変更頻度の隔離、所有権、信頼、実行時の障害、またはデプロイといった具体的な必要性のために境界を設けます。アダプターの数は根拠にはなりますが、ルールではありません。本番用アダプターと、それに忠実なテスト用インタラクターがあれば境界を設ける根拠になり得ますが、偶然できた2つのラッパーでは不十分です。
安定したコントラクトをテストする
呼び出し元から観測できる振る舞いを、主なテスト対象にします。プライベートヘルパーをエクスポートしたり、テストだけを目的に内部の境界を公開したりしないでください。プライベートなサブシステム自体が一貫性のあるモジュールである場合や、アルゴリズムで障害箇所を正確に特定する必要がある場合は、焦点を絞ったテストを設けてもかまいません。ただし、そのようなテストで偶発的なオーケストレーションを固定化してはなりません。
意図的に薄い境界を保つ
ルートのリーフ、CLI コマンド、アダプター、生成されたクライアント、またはコンポジションルートが薄いという理由だけで、浅いと判断しないでください。変換や配線は、薄くあるべき場合が多くあります。ポリシーが適切な内部モジュールに隠されているか、またその境界を越えてプロバイダーやトランスポートの知識が漏れていないかを判断します。
ワークフロー
1. 役割と呼び出し元を定義する
そのモジュールが担う振る舞い、現在と想定される呼び出し元、一般的なユースケース、外部に残すべきものを明確にします。新しい名前を付ける前に、プロジェクトの指示、アーキテクチャ上の判断、用語集の規約、関連するテストを確認します。
2. コントラクト全体の負担を洗い出す
各呼び出し元が現在把握しなければならないことを列挙します。
- 操作とデータの形状
- インバリアントと事前条件
- 呼び出し順序とライフサイクル
- 設定と依存関係の構築
- エラー、再試行、部分的な失敗、作用
- レイテンシー、スループット、一貫性、トランザクションに関する期待
型シグネチャが短いことを、インターフェースが小さいことと混同しないでください。
3. 隠れた知識と漏れている知識を把握する
呼び出し元と協力関係にあるモジュールをたどります。重複する判断、中継の連鎖、プロバイダーの型、繰り返されるオーケストレーション、同時に変更されるファイル、内部を再構成しなければならないテストを特定します。独立した所有権、異なる障害領域、または実際に別個のポリシーを必要とする呼び出し元など、反証となる要素も記録します。
4. シームを設ける前に責務を決める
モジュールの一貫した責務を定義する一文を書きます。どの知識をその背後に置くかを決めてから、シームを配置し、依存関係の戦略を選択します。既存のまとまりについては references/deepening.md を読んでください。
重要な汎用依存関係またはサブシステムがまだ指定されていない場合は、依存関係を前提とする契約を確定する前に、この責務、呼び出し元のシナリオ、作用、制約を evaluate-existing-solutions に渡してください。そうすることで有用な変更境界を保てる場合は、選択したプロバイダーやライブラリをローカルなアプリケーションの言葉で表現する層の背後に置きます。安定したプリミティブをすべて反射的にラップしたり、ベンダーの API をモジュールの契約にそのまま複製したりしないでください。
5. 使用シナリオから契約を設計する
無効な入力、一部の失敗、キャンセル、再試行、関連する場合はライフサイクルも含め、代表的な呼び出し元の例をもとに設計します。メソッドだけでなく、型、不変条件、順序、エラー、作用、パフォーマンスへの期待も明記します。
重大な選択を行う際は、references/design-it-twice.md を読み、実質的に異なる設計を比較してから推奨案を提示してください。
6. 深さに関する主張を検証する
次の点を確認します。
- 一般的な呼び出し元が把握することや調整することは減りましたか?
- 実装の知識は契約の背後に移りましたか。それとも単に名前が変わっただけですか?
- ポリシーを変更するとき、変更はここに集約されますか。それとも広範囲に波及しますか?
- 作用や失敗を正直に表現できていますか?
- 忠実な依存関係を使い、契約を通じて振る舞いをテストできますか?
- 先回りしすぎた柔軟性や、汎用コマンド型の万能インターフェースを避けられていますか?
7. 実装の引き渡し先を担当スキルに振り分ける
- 公開 HTTP と、所有権の境界を越えて公開またはバージョン管理される契約には
api-designを使用します。通常のプロセス内コンポーネントの props は、このスキルと該当するフレームワーク/デザインシステムのガイダンスで扱います。 - ファイル/パッケージの配置と、機械的な依存関係の強制には
structure-codebaseを使用します。 - 現在の構成がまだ可視化されていない場合は、最初に
render-code-shapeを使用します。引用付きの境界、シグネチャ、呼び出しグラフが出力され、これらは本スキルの判断に必要な根拠となります。 - 受け入れる結果として、呼び出し元の効果を改善するだけでなく、分岐、状態、依存関係、レイヤー、運用上の可動部分の総数を減らす必要がある場合は
reduce-system-complexityを使用します。 - 責務と制約が判明した後も重要な汎用実装の選択が未解決の場合は、
evaluate-existing-solutionsを使用します。 - 意図的なアクター間のやり取りを伴う、採用を選択したポート&アダプター方式のシステムに限り、
hexagonal-architectureを使用します。 - 既存のハードコードされた依存関係がテストハーネスの妨げになっている場合は、
finding-seamsを使用します。 - テストされていない振る舞いを再構成する前に、
characterisation-testsを使用します。 - 実装中は、振る舞いを変更するかどうか、およびセーフティネットの信頼性に応じて、
tdd、testing、refactoringを使用します。PR の準備段階では、リポジトリのミューテーションポリシーに従います。ミューテーションが有意義でない場合は、それに見合った代替の根拠を記録します。 - ドメイン用語を提案または変更する必要がある場合は、
ubiquitous-languageを使用します。黙って新たな用語を作らないでください。
設計の成果物
次の項目を作成します。
- モジュールの責務を定義する一文と、明示的な対象外事項。
- 呼び出し元と代表的な使用シナリオ。
- 型以外の義務も含む、提案する契約の全体。
- その背後に隠す知識と振る舞い。
- 依存関係、シーム、アダプターまたはテスト用インタラクター、およびそれらの忠実性を確保する戦略。
- 検討した代替案と、推奨案を選ぶ理由。
- 互換性と段階的な移行に関する制約。
- 実装の変更後も維持すべき振る舞いテスト。
- 前提、リスク、さらに必要な根拠。
完了チェック
- 提供する一貫した動作に比べて、契約はよりシンプルですか?
- オーケストレーションの調整項目を明らかにするのではなく、判断を隠していますか?
- よくあるケースは明確でありながら、まれなケースも不可能にしていませんか?
- 凝集度、所有権、障害、実行時の境界は、実態を正直に反映していますか?
- 境界は、実際の変化や分離の必要性によって正当化されていますか?
- 呼び出し側と動作テストは、同じ安定した契約を利用できますか?
- 薄い境界は、薄くポリシーを持たないままですか?
- 互換性や移行に関するすべての主張に、検証手段がありますか?
クレジット
Matt Pocock の MIT ライセンスの codebase-design スキルとリンク先のリソースをベースにしています。deep-module と Design It Twice の概念は John Ousterhout に、seam の用語は Michael Feathers に謝意を表します。固定された出典とライセンス条件については、references/source-notes.md と LICENSE を参照してください。