テストは全部通った。
レビューも通った。ドキュメントもある。
なのに、誰も「これが正しい」と保証できていなかった。
AI駆動開発を数ヶ月やった人なら、この奇妙な感覚に覚えがあるはずだ。速度は確かに上がった。だが、品質を支えていた「前提」が、静かに一つ壊れている。この記事は、その壊れる瞬間を実際に再現してみる話だ。
「正しいけど無意味な指摘」が返ってきた日
きっかけは、AIにコードレビューをさせたことだった。
返ってきた指摘は、どれも正しかった。「この変数はnullになり得る」「この分岐は到達不能では」——理屈は通っている。なのに、半分が無意味だった。
理由は単純で、私はAIにコードしか渡していなかった。configファイルを読ませていなかったのだ。そのnullは、設定ファイルで必ず埋まる。その分岐は、環境変数で確実に通る。
AIは「コードの中では正しい」指摘をした。だが「このシステムでは正しい」指摘ではなかった。
このとき気づいた。レビューの品質を決めていたのは、AIの賢さではなかった。AIが「何と照らし合わせたか」 だった。コードをコードだけと照らし合わせれば、正しいが無意味な指摘が出る。照合する相手が欠けていたのだ。
そして、もっと嫌なことに気づいてしまった。
実装者とテスト作成者が、同一人格になっている
AIに実装とテストを同時に書かせているとき、私は毎回これをやっている。
テストが照らし合わせている「正解」は、仕様ではない。AI自身がさっき書いたコードの解釈だ。
昔のソフトウェア開発には、独立した3者がいた。
- 仕様:何を満たすべきかを決める
- 実装:それをコードにする
- テスト:本当に満たしたかを検証する
この3者が別々だったから、テストは意味を持っていた。実装がサボれば、仕様から作られたテストが赤くなる。照合相手が独立していたから、検証が成立していた。
AI駆動開発では、この3者が1人格に畳まれる。プロンプト → AI実装 → AIテスト。実装を書いたAIが、そのままテストも書く。すると何が起きるか。実際に回してみよう。
再現実験:曖昧な仕様を、AIに丸投げする
人間が与えた仕様は、たった1行。わざと曖昧にしてある。
「税込価格を計算する関数。端数は処理して。」
AIはこれを「端数=四捨五入」と解釈し、実装とテストを一括生成した。
# ① AIが生成した実装(「端数」を四捨五入と解釈)
def tax_included_price(price: int, rate: float = 0.10) -> int:
return round(price * (1 + rate))
# ② AIが同時に生成したテスト(同じ「四捨五入」の解釈から作られている)
def test_tax_included_price_ai():
assert tax_included_price(100) == 110 # 110.0 → 110
assert tax_included_price(105) == 116 # 115.5 → 116
assert tax_included_price(198) == 218 # 217.8 → 218
このテストは、当然、全部通る。
[AIのテスト] 全部通った ✅ ── コードは「正しい」ように見える
ところが、私のシステムの仕様では「切り捨て」と決まっていた。そもそも消費税の端数処理は、切り捨て・切り上げ・四捨五入のどれを採用してもよい(法律はどれも認めている)。だからこそ「端数は処理して」の一言では、どれを選ぶべきか決まらない。 それでも私は、その一言しか渡さなかった。
AIの実装を、本来の仕様(切り捨て) と照らし合わせてみる。
import math
def tax_included_price_spec(price, rate=0.10):
return math.floor(price * (1 + rate)) # 仕様: 切り捨て
price= 105 AI実装= 116 仕様= 115 ★ズレ
price= 198 AI実装= 218 仕様= 217 ★ズレ
price= 333 AI実装= 366 仕様= 366 OK
price= 1050 AI実装= 1155 仕様= 1155 OK
105円が、116円になっている。仕様なら115円。 消費税の計算が1円ずれている。しかもAIのテストは、緑のままだ。
テストは検証ではなく、「自己肯定」になっていた
テストが緑になったのは、コードが正しいからではない。
テストとコードが、同じ勘違いを共有していたからだ。round() で実装し、round() の結果でアサートすれば、通るに決まっている。
これは検証ではない。自分の答案を、自分の解答用紙で採点しているだけだ。そしてこの採点に、人間は一切関与していない。仕様(切り捨て)を知っているのは人間だけなのに、だ。
(余談だが、AIが選んだ round() は、正確には一般的な四捨五入ですらない。Pythonの round() は銀行丸め——ちょうど .5 のときは偶数側へ寄せる方式——を採用している。round(114.5) == 114、round(115.5) == 116 だ。AIの解釈は二重にズレていた。それでもテストは緑だった。)
会計監査には職務分掌という鉄則がある。仕訳を切る人と、それを監査する人は、絶対に別人でなければならない。自分の仕事を自分で監査したら、それは監査ではないからだ。AI駆動開発は、この鉄則を静かに破っている。被監査人と監査人が、同一人格なのだ。
言い換えれば、AI駆動開発の品質問題は、もはやソフトウェア工学というより、内部統制や安全工学の問題に近づいている。「良いコードを書く」話ではなく、「誰が誰を検証するか」という統制の設計の話なのだ。
「じゃあ別のAIにテストを書かせれば?」
ここで必ず出てくる反論がある。実装したAIとは別のAIに、テストを書かせればいいのでは、と。
半分は正しい。だが、それだけでは独立性は戻らない。GPTもClaudeもGeminiも、学習データも設計思想も近い。同じ問題で、同じ勘違いを、同じ自信ですることがある。原発や航空の安全工学では、これを Common Mode Failure(共通原因故障) と呼ぶ。独立した系統が並んでいても、同じ原因で同時に壊れるなら、独立性はゼロだ。
独立性とは、検証者の人数ではない。誤りの相関の低さのことだ。AIを何個並べても、相関が高ければ独立性は増えない。この話は次回、じっくり書く。
では、どうするか——テストを「独立」させる
答えはシンプルで、照合する相手を、生成する相手から切り離すことだ。
具体的には、こうなる。
- AIの出力を見る前に、人間が受け入れ基準(期待値)を書く。「税込105円は115円」と、仕様側から先に決めてしまう
- 生成と原理の違う検証を足す。property-based test、fuzzing、実行時assert、本番監視。AIの解釈とは別の経路で正しさを突く
- テストの「期待値」だけは、AIに書かせない。そこが唯一、仕様と接続している場所だから
つまり、AI時代に人間へ残る仕事は、実装ではない。独立した検証基準を供給することだ。何を満たすべきかを握り、AIの答案とは別の解答用紙を用意する。コードを書く人から、正解を定義する人へ——役割はそちらに移っていく。
AIはコードを書くのが速い。テストを書くのも速い。だが、速さは検証の独立性を保証しない。むしろ、実装とテストを同じ人格が一気に生む分、検証が自己肯定にすり替わる瞬間を、私たちは毎日大量生産している。
テストが全部通ったとき、一度だけ疑ってみてほしい。
それは「正しい」のか、それとも「同じ勘違いを共有している」だけなのか。
この記事は「AI駆動開発における独立性崩壊」シリーズの第1弾です。
次回:git blame の先にいたのは、人間ではなくプロンプトだった ——AIが奪った「なぜそう書いたか」の話。