前回の記事では、ChatGPTとClaude CodeをGitHubでつなぎ、役割を分けて一人開発をチーム開発のように回す話を書きました。ただ、役割を分けても、土台のリポジトリが散らかっていると、AIはそこを速いスピードで荒らしていきます。今回はその続きで、リポジトリの構造の話です。
結論
AI駆動開発では、AIにどう指示するかより、リポジトリそのものの構造のほうが品質に効いてきます。自分が効いたと感じているのは、次の3つです。
- 依存方向を固定する(DDD/クリーンアーキテクチャのエッセンス)
- 1つの知識は1か所にだけ書く(SSOT)
- 1と2を、AIの善意に頼らず機械的に検証する
AIはリポジトリをどう汚すのか
人間の開発者は、ルールが書かれていなくても「ここにこれを書くと後で困る」と察して避けます。AIにはその暗黙知がありません。自分が実際に見たのは、次のようなパターンです。
- 置き場所の迷い:似た責務のファイルが複数あると、「それっぽい」ほうに書く。結果、同じ種類の処理がばらばらの場所に増える。
- レイヤ越えの依存:ビジネスロジックから、手近だからという理由で外部APIクライアントを直接呼ぶ。
- ロジックの複製:既存の処理を探すより、新しく書くほうが速いので、似た計算をもう1つ作る。
- 文書の二重管理:同じ説明をREADME、設計書、CLAUDE.mdにコピーする。しかもAIは、これを人間よりずっと速くやります。
どれも、1回なら小さな問題です。ただ、AIは1日に何十回も変更を重ねるので、構造の乱れは複利で効いてきます。
柱①:境界を明確にする
DDDやクリーンアーキテクチャを、フルセットで入れる必要はありません。自分が取り入れたのは、次の3点だけです。
1. 依存方向を一方向に固定する
presentation → application → domain を基本とし、逆向きの依存は禁止にします。外部サービス(自分のリポジトリならデータ提供元のAPI)は、インフラ層の1か所に閉じ込めます。
2. 業務ルールを外部I/Oから切り離す
domainにはネットワークやDBへのアクセスを持たせません。純粋なロジックだけにしておくと、AIが変更したときに「どこまで影響するか」が読みやすくなり、レビューの範囲も絞れます。
3. 抽象をapplication側に置き、実装をinfrastructure側に置く
インターフェースは、それを使う側が所有します。applicationは「こういう機能が欲しい」というインターフェース(ポート)を自分の側で定義し、具体的な実装(infrastructure)は知りません。infrastructureがそのポートを実装する形にして、依存の向きを逆転させます(依存関係逆転の原則)。自分のリポジトリでは、ポートを専用のディレクトリにまとめ、依存チェックでは独立したレイヤとして扱っています。これで「infrastructureが依存してよいのは、ポートとdomainだけ」とテストで書けます。
これだけで、AIが新しいコードを書くときの選択肢がかなり減ります。「外部APIを呼ぶコードはここ」「ビジネスルールはここ」と決まっていれば、迷って間違える余地が小さくなります。
※ エバンズのDDDによるとinfraの抽象はDomain層に置くことが多いですが、クリーンアーキテクチャではApplication層に置くことが多いです。今回はクリーンアーキテクチャの流儀に従いました。
柱②:SSOT(Single Source of Truth)
境界がコードの置き場所の話だとすれば、SSOTは知識の置き場所の話です。
なぜAI駆動開発でSSOTが重要なのか
- AIは、読んだ文書をどれも同じ重さで信じます。同じ説明が2か所にあり、片方が古くなっていると、AIは古いほうに従って実装することがあります。
- AIは文章を書くのも得意なので、放っておくと説明を複製して増やします。文書のズレは、人間が書いていたころより速く生まれます。
- 文書が長いと、AIが読む入口が重くなり、本当に必要な指示が埋もれます。
自分が決めたルール
- 1つの概念の詳細は、正規文書1つにだけ書く。他の文書からは、短い要約と正規文書へのリンクだけにする。
- CLAUDE.mdは薄くする。プロジェクトの責務、開発フロー、守るべき原則、参照先の一覧だけを置く。個別機能の詳細仕様や関数単位の説明は書かない。
- README、設計規約、運用手順書、調査メモを役割ごとに分ける。「新しい判断を書くとき、どの文書に属するか」を最初に決める。
- 変更の経緯や理由は文書に書かず、Issue/PR/Gitの履歴に残す。文書には「現在の正しい状態」だけを書く。
最後のルールは地味ですが効きます。「以前はこうだったが、こう変えた」という物語が文書に混ざると、AIは過去の状態を現在の仕様と取り違えやすくなります。
コードにもSSOTがある
同じ原則はコードにも当てはまります。たとえば、過去データでの検証(バックテスト)をするとき、ランキングのロジックを検証用にコピーして書き直したくなります。ですが、それをやると、本番のロジックと検証のロジックがずれて、検証結果が信用できなくなります。
自分のリポジトリでは、検証側は既存のランキング処理を「日付を指定して呼ぶだけ」にしています。ロジックの正規の置き場所を1つにしているわけです。
境界(DDD/CA)とSSOTは、別々の話に見えて、根は同じです。1つの変更が、複数の場所に波及しない構造にする。この原則を、コードでは境界、知識ではSSOTとして適用している、と自分は考えています。
柱③:「守れ」ではなく「守らせる」
ルールを書いても、AIが毎回守ってくれる保証はありません。人間も同じです。だから、ルールはテストにします。
自分のリポジトリでは、import文をAST解析して、レイヤ間の依存が許可リストに収まっているかを検証しています。許可される依存は、たとえば次のように明示してあります。
ALLOWED_LAYER_EDGES = frozenset({
("presentation", "application"),
("presentation", "domain"),
("application", "application_port"),
("application", "domain"),
("infrastructure", "application_port"),
("infrastructure", "domain"),
# ...
})
そして、違反がゼロであることをテストで確認します。
def test_no_layer_boundary_violations() -> None:
result = analyze_import_graph.analyze(analyze_import_graph.SRC_ROOT)
assert result["boundary_violations"] == []
許可リストにない依存(たとえば domain → infrastructure や application → infrastructure)が1本でも増えると、このテストが落ちます。AIが「手近だから」とレイヤを越えたコードを書いても、テストが止めてくれます。
ポイントは、許可リストを「唯一の正規の定義」にしていることです。ここでもSSOTが使われています。ルールの文章と検証コードが別々にあると、ずれるからです。
テストで機械的に守れないものは、レビューの観点に入れています。たとえば、共通化すべき重複コードが放置されていないか、といった項目です。
構造が整うと、レビューが楽になる
境界とSSOTが決まっていると、レビューでも見るべき点が絞れます。
- 依存の向きはテストが見てくれるので、レビュー担当は、業務ロジックの正しさに集中できる。
- 「この説明はどこに書くべきか」の判断基準が決まっているので、レビューの指摘も一貫する。
前回の記事で、ChatGPTにレビューを任せる話を書きました。レビュー担当のAIが迷わずに指摘できるのは、リポジトリの構造がレビューの物差しになっているからだと思います。
やりすぎには注意
- 小さな個人開発で、Entity, Value Object, アグリゲートやドメインイベントまで厳密に定義し始めると、AIも人間も疲れます。自分のリポジトリも、入れているのはレイヤ分離と依存方向の機械的な検証が中心です。
- SSOTも、文書を細かく分けすぎると、「どこに書くか」の判断コストが上がります。1概念1文書の「概念」の粒度は、最初は粗く決めて、困ったら分けるくらいで十分です。
- ルールを増やすほど、メンテナンスの対象も増えます。守らせる仕組みは、本当に壊れると困る箇所だけに絞るのがよいと思います。
まとめ
- AI駆動開発では、プロンプトの工夫より、リポジトリの構造が品質に効く。
- 取り入れたのは、DDD/CAのエッセンス(依存方向の固定、業務ルールの分離、外部サービスの隔離)と、SSOT(1つの知識は1か所)。
- どちらも「1つの変更が複数箇所に波及しない構造にする」という同じ原則から来ている。
- ルールは書くだけでなく、テストで守らせる。
AIは速いので、構造の乱れも速く広がります。だからこそ、人間が最初に「境界」と「正規の置き場」を決めておく価値が大きいと感じています。
同じようなことをされている方がいれば、ぜひお問い合わせから教えてください。
最後まで読んでいただきありがとうございます。ご質問やコメントはお問い合わせからよろしくおねがいします。