間違った指標
AI生成コードの品質に関する議論の多くは、生成時点での正確性に焦点を当てている。出力はコンパイルできるか?テストに通るか?仕様に合致するか?
これらは最低限の条件にすぎない。本当のコストについては何も教えてくれない。
本当の指標は置換可能性だ。要件が変わったとき、このmoduleを削除して同じcontractの背後に再実装するコストはどれほど安いか?
答えが「些細なこと」なら、AIの速度は複利的に効いてくる。答えが「まず6つの暗黙的な依存関係を追跡する必要がある」なら、買ったと思っていた速度の優位性はすでに失われている。
AIコードが結合に傾きやすい理由
大規模言語モデルは目前の要求に対して最適化する。将来の変更に対しては最適化しない。
ログインフローをプロンプトすると、動くログインフローが得られる。FirebaseからSupabaseへ、あるいはカスタムJWTサービスへ、それを利用するどの画面にも触れずに切り替えられる認証interfaceの背後にあるログインフローは得られない。
これはモデルの失敗ではない。コンテキストの失敗だ。モデルは置換可能性に対して最適化するよう求められなかったので、しなかっただけだ。
結果として、AI生成コードはデフォルトで密結合に傾く。モデルが悪いからではなく、次トークン予測器にとって分離は最も抵抗の少ない道ではないからだ。
置換可能性はアーキテクチャの決定である
置換可能性は偶然には生まれない。意図的な構造上の選択だ:
- すべての外部依存関係は、アプリが所有するinterfaceの背後に置かれる。
- すべての生成されたmoduleは実装ではなくcontractを公開する。
- すべてのオプション機能は注入され、直接インポートされない。
- すべての合成決定は、50個のファイルに散らばるのではなく、一つのcomposition rootに集約される。
これは新しいことではない。基本的な依存性逆転だ。新しいのは、AI生成コードによってこれらの原則への違反が容易になり、何かを変更する必要が出るまで見えなくなることだ。
複利効果
すべてのmoduleが置換可能な場合:
- 悪い生成結果のコストは日単位ではなく分単位だ。
- ベンダー変更はrewriteではなくinterfaceの差し替えだ。
- AIの速度は反復を重ねても対数的ではなく線形に維持される。
- チームが「同じcontractの背後でこれを再生成して」と言って、それが本当に実行できる。
moduleが絡み合っている場合:
- すべての変更に完全な依存関係グラフの理解が必要になる。
- AI支援のrefactorはすべて、無関係なシステムを壊すリスクがある。
- 爆発半径が予測不能なため、チームは徐々にAI出力を信頼しなくなる。
- 速度はAI以前のレベルに戻るが、維持すべきコードは増えている。
実践的テスト
AI生成のmoduleをcodebaseに受け入れる前に、一つのテストを適用せよ:
このファイルを削除し、公開しているinterfaceだけを使ってゼロから再実装した場合、利用者を一切修正せずに済むか?
はいなら、出荷せよ。いいえなら、出荷前に境界を修正せよ。
これはenforcementのコストが低い。composition rootとinterface優先の設計があれば、この性質は構造的に得られる。精巧なガバナンスは不要だ。一つのアーキテクチャルールを一貫して適用すればよい。
AI-Nativeアーキテクチャとの関連
この広範な問題についてはStanford CS146S Is Right About AI Coding — The Missing Subject Is Architectureで書いた。置換可能性の原則は、AI-nativeなcodebaseをバージョン1以降も存続可能にする具体的なメカニズムだ。
Stanfordは開発者にAIツールの効果的な使い方を教えている。それは重要だ。しかし、置換可能性のないツール習熟は罠だ。codebaseが変更コストの高いものになるまでは速く出荷できるが、そうなるとAIをまったく使わなかったチームよりも遅く出荷することになる。
規律は「より良くプロンプトすること」ではない。規律は「プロンプトのミスが安く取り消せるようにアーキテクチャを設計すること」だ。
FAQ
AI生成コードにとって「置換可能なアーキテクチャ」とは何を意味するか?
置換可能なアーキテクチャとは、すべてのAI生成moduleが、システムの他の部分が依存するinterfaceの背後に配置されること — 実装そのものではなく — を意味する。要件が変わったり生成が間違っていたりした場合、利用者に触れずにそのmoduleを削除して再実装できる。
AI生成のcodebaseで置換可能性をどうenforceするか?
3つのメカニズム:アプリケーションが所有するinterface(依存先ではなく)、すべての実装が接続される単一のcomposition root、そしてmoduleが他のmoduleの内部を直接インポートするとCIが失敗するチェックだ。
置換可能性は初期開発を遅くするか?
いいえ。実装を生成する前にinterfaceを定義するのに数秒かかるだけだ。それをしなかったコストは、ベンダーの切り替えやrefactorが完全なrewriteに変わる数週間後に現れる。
これは依存性注入と同じか?
依存性注入は置換可能性を実現するための一つのメカニズムだが、全体像ではない。置換可能性にはcontract tests、境界の検証、composition rootも必要だ — コンストラクタのパラメータだけではない。