Site Overlay

AI駆動開発では、リポジトリの「境界」と「正規の置き場」が品質を決める

前回の記事では、ChatGPTとClaude CodeをGitHubでつなぎ、役割を分けて一人開発をチーム開発のように回す話を書きました。ただ、役割を分けても、土台のリポジトリが散らかっていると、AIはそこを速いスピードで荒らしていきます。今回はその続きで、リポジトリの構造の話です。

結論

AI駆動開発では、AIにどう指示するかより、リポジトリそのものの構造のほうが品質に効いてきます。自分が効いたと感じているのは、次の3つです。

  1. 依存方向を固定する(DDD/クリーンアーキテクチャのエッセンス)
  2. 1つの知識は1か所にだけ書く(SSOT)
  3. 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だけ」とテストで書けます。

レイヤの依存方向presentationはapplicationに、applicationはdomainとapplication_portに依存する。infrastructureはapplication_portとdomainに依存する。矢印は依存する向きを表す。presentationapplicationdomainapplication_portinfrastructureポートを使うポートを実装する(依存の逆転)
図:レイヤの依存方向(矢印は「依存する側 → される側」。主な依存のみ表示)

これだけで、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は速いので、構造の乱れも速く広がります。だからこそ、人間が最初に「境界」と「正規の置き場」を決めておく価値が大きいと感じています。

同じようなことをされている方がいれば、ぜひお問い合わせから教えてください。

最後まで読んでいただきありがとうございます。ご質問やコメントはお問い合わせからよろしくおねがいします。