1万行を書いて、中身を知らない
AIが書いたコードは、読まなくても信用していい。ただし条件がある。それが、この記事の結論です。
先日、Claude Codeでバックエンドとフロントエンドを合わせて約1万行を書き、期限の日に間に合わせました。正直に言うと、中身はほとんど把握していません。
TDDもしていません。書かせただけです。そして、書かせた結果を疑ってもいませんでした。理由は、決定的だからです。一度書かれたコードは、同じ入力には毎回同じように動く。だから大丈夫だろう、と。
不思議なことに、「読めない」焦りはありませんでした。焦っていたのは「説明できない」ことです。ビジネス側に「これ、どういう時にこうなるの」と聞かれて答えられない。障害が起きたときに、どこが悪いかを言えない。困るのはいつもそっちです。要求する側も、コードを全部見せろとは言ってきません。聞かれるのはいつも、どう動くかです。
この違和感をたどっていくと、ひとつの問いに行き着きました。私たちは、読んだこともない機械語を、なぜ毎日信用しているのか。
読んでいない機械語を信用できる4つの理由
ほとんどのエンジニアは、自分のコードがどんな機械語になっているか見たことがありません。それでも疑わないのは、確かめたからではなく、次の4つが揃っているからです。
- 実績:コンパイラもCPUも、世界中で何十年、何十億回と動いてきた。壊れたら誰かが気づき、直してきた。
- 仕様:命令セットにも言語にも仕様があり、コンパイラはそれに対して大量のテストで検証されている。仕様で定義された範囲では、同じ入力から同じ振る舞いが出る。
- 分業:全員が疑わなくても、疑う人がいる。コンパイラの開発者、セキュリティ研究者、バグを報告する利用者。残りの私たちは、その人たちが疑ってくれていることを信用している。
- 追跡可能性:まれにバグが起きても、どこで何が起きたかを特定できる。1994年のPentiumの割り算バグも、2018年のSpectreも、原因が特定され、対策が配られた。
つまり、信用の根拠は出力そのものではなく、出力を生む仕組みにあります。
もっとも、完全な信用ではありません。Ken Thompsonは1983年度チューリング賞の受賞講演「Reflections on Trusting Trust」(1984年発表)で、コンパイラ自体に裏口を仕込めば、ソースをどれだけ読んでも見つからないと示しました。私たちが機械語を信じているのは、確かめるコストが高すぎて、しかもこれまで信じて大丈夫だったからです。
そして、この講演そのものが疑う人の仕事でした。みんなが信じているところを、誰かが疑い続けている。信じて大丈夫だったのは、そのおかげです。
層で見ると、非決定的なのは一番上だけ
自分の書いたものがCPUで動くまでを層で並べると、こうなります。
第3言語 指示と仕様(人間が書く)
│ AIが訳す ……… 毎回少し違う・実績はまだない ← 検証はここの出口に置く
▼
第2言語 TypeScript / React(AIが書く)
│ コンパイラ・JSエンジン ……… 何十年の実績
▼
第1言語 機械語
│ CPU ……… 何十年の実績
▼
実行
下の2つの矢印は、何十年も信用を積んできた翻訳です。一番上の、AIによる翻訳だけが毎回少し違う結果を出し、信用の実績もまだありません。だから検証は、その矢印の出口に置きます。
細かく言えばTypeScriptは一度JavaScriptになり、JSエンジンが実行時の様子を見ながら機械語にします。生成される機械語は毎回同じとは限りませんが、振る舞いは言語仕様で決まっていて、そこは検証済みの側です。
決定的であることと、正しいことは別
私が1万行を疑わなかった理由、「決定的だから」は半分だけ正しかったと思います。
AIの生成は毎回少し違いますが、生成されたコードは決定的です。同じ入力には毎回同じように動く。ここまでは合っています。言い換えると、生成されたコードは、確率から偶然引いた一回分を、決定的に固めたものです。
でも、決定的なのは同じように動くことの保証であって、正しく動くことの保証ではありません。決定的なコードは、間違えるときも決定的に、毎回同じように間違えます。
コンパイラを信用できるのも、決定的だからだけではありません。決定的で、しかもその振る舞いが仕様に照らして検証されているからです。最初に挙げた4つの理由のうち、決定性は「仕様」の一部でしかありません。あの1万行には、決定性はあっても、それが私の意図どおりかを確かめた人が誰もいませんでした。疑う人が、一人もいなかったのです。
逆に言えば、決定的であることはむしろ追い風です。決定的なコードなら、一度通ったテストは何度やっても通る。テストに意味があるのは、相手が決定的だからです。足りなかったのは決定性ではなく、それを確かめる仕組みのほうでした。
「AIは決定的じゃないから、人間がコードをレビューすべきだ」という意見もよく聞きます。前半は正しいと思います。生成が決定的でないので、書き手のAIを一度確かめても、次の出力は保証されません。出力は毎回疑う必要があります。でも、そこから「人間が毎回コードを読む」までは一足飛びです。生成されたコードは決定的なので、疑う手段には決定的なテストが使えます。そもそも要求する側が求めているのも、コードを読んだという事実ではなく、どういう時にどう動くかを説明できることです。
本当のソースコードは「第3言語」
AIと開発していると、言語の層がひとつ増えたことに気づきます。
- 第3言語:自分が書く短い指示と仕様
- 第2言語:TypeScriptやReactのコード
- 第1言語:機械語
昔はプログラマーが第2言語を書き、コンパイラが第1言語に訳していました。今は自分が第3言語を書き、AIが第2言語に訳し、そこから先は昔と同じ道を通ります。第2言語は、かつてのアセンブラと同じ「中間の言語」の位置に下がりました。
そう考えると、説明できない焦りの正体が見えます。説明すべきなのは、AIが書いた1万行ではなく、第3言語のほうです。ところが第3言語は、頭の中とチャットの履歴に散らばっていて、どこにもまとまっていない。ソースコードが保存されていない状態なので、説明しようとすると1万行を読みに行くしかなくなります。
ただし、第3言語は穴だらけの仕様
ここで、コンパイラのたとえが一か所だけ崩れます。
TypeScriptから機械語への翻訳は、意味を変えない翻訳です。TypeScriptには厳密な仕様があり、書いていないことは起きません。一方、第3言語は自然言語で、書いていないことだらけです。「ログインできるようにして」と書いたとき、パスワードを何回間違えたらロックするか、セッションは何分で切れるか、AIは何かを選んで埋めます。
つまりAIは、訳しているだけでなく、仕様の穴を埋める決定をしています。ビジネス側の「これ、どういう時にこうなるの」に答えられなかったのは、たいていこの穴の部分です。自分が決めていないのだから、説明できるはずがありません。
なので、対策は二段になります。
- 第3言語をリポジトリに残す
- 仕様と、決めたことと、なぜそう決めたかを短いメモで残す(CLAUDE.mdや設計メモ)
- 機能ごとに、AIへの指示の要点を残す
- 機能ごとに「何をするか、データはどこから来てどこへ行くか、テストで何が保証されているか、どこを信用してどこを疑っているか」の4点を書けるようにする
- AIが埋めた穴のうち、大事なものをテストで固定する
2つ目が、次のTDDの話です。
TDDは「信用の基準」を人間が書く仕組みになる
下の層が信用できるのは、世界中で使われて検証されてきたからです。自分のプロジェクトのコードには、その実績がありません。だから、TDDで「このプロジェクト専用の検証の実績」を作ります。テストが、世界中の利用者の代わりをするわけです。
もうひとつの見方をすると、テストは第3言語のうち、穴のない部分です。自然言語の指示は曖昧ですが、「5回間違えたらロックされる」というテストは曖昧になりようがありません。仕様の穴は、テストを書いた分だけ人間の決定で埋まります。
AI以前のTDDは、人が書くコードを良くするための作法でした。AI以後は、「何が通れば信用していいか」を、AIが書く前に人間が決めておく仕組みになります。人間は実装ではなく、信用の基準を書く側に回ります。TDDを提唱したKent Beck自身も、AIエージェントと組むときTDDは「超能力」になると語っています。同時に、エージェントがテストを通すためにテストを消そうとする、とも。
ただし、成立には条件があります。
- 何をテストするかは人間が決め、テストの変更は人間が承認する:AIはテストに合わせにいきます。テストを通すためだけの特殊処理を書いたり、テスト自体を書き換えたりする。テストコードを書く作業はAIに手伝わせてもいいですが、何を確かめるかは人間が決め、変更は必ず人間が見ます。お願いではなく仕組みで止めるのが確実です。Claude Codeならpermissionsでテストディレクトリの編集を禁止できますし、GitHubならCODEOWNERSでテストの変更にレビューを必須にできます。
- テストの強さを確かめる:わざとコードを壊してテストが落ちるか見るミューテーションテスト(JS/TSならStryker)、入力を大量に投げるプロパティベーステスト(fast-check)が効きます。
- 画面は画面単位で縛る:Reactならコンポーネント単位のテストと、実際に操作するE2Eテストを組み合わせる。壊れたときに「この画面のここ」と場所が言えます。
これが揃えば、AIが書いた層を、読まずに下の層と同じように扱えます。
4つの理由を、AIの層に移す
最初に挙げた「機械語を信用できる4つの理由」は、AIの層でも自分で作れます。
| 機械語の層 | AIの層で作るもの |
|---|---|
| 実績:世界中で何十年も動いてきた | 人間が決めたテストが、毎回通っていること |
| 仕様:言語と命令セットの仕様 | リポジトリに残した第3言語と、穴を埋めたテスト |
| 分業:疑う人がいる | 疑う役を置く。書いたAIとは別の目によるレビュー |
| 追跡可能性:どこで何が起きたか言える | 指示とコミットの対応、画面単位のテスト |
4つ目は見落としがちです。どの指示からどのコードが出たかが辿れれば、障害のときに「この指示のこの解釈が悪かった」と言えます。指示ごとに小さくコミットし、コミットメッセージに指示の要点を残すだけで、かなり変わります。
「意図通り動く」と「やってはいけないことをしない」は別のゲート
AIが決定的なコードを出し、それが意図通りに動くと確かめられているなら、それでOKだと思います。ただし、それがセキュリティに違反していたらダメです。検証には軸が2つあります。
| 軸 | 問い | 確かめ方 |
|---|---|---|
| 機能 | 意図通り動くか | 仕様に沿ったテスト(TDD) |
| セキュリティ | やってはいけないことをしていないか | 別のゲート:静的解析、依存関係の脆弱性スキャン、シークレット検出、認可まわりの専用テスト |
テストは「できること」を確かめるのは得意ですが、「していないこと」を確かめるのは苦手です。認可を飛ばしていないか、入力を素通しにしていないか、秘密情報をログに出していないか。意図通りに動きながら、同時に穴が開いている、ということは普通に起きます。
もう一つ大事なのは、書いたAIとは別の目で見ることです。書いた本人に自分の穴を探させると、同じ前提で見落とします。
そして、機能のテストは「こう動くべき」を書くのに対し、セキュリティのテストは「こう使われたら困る」を書きます。後者、つまりアビューズケースは、悪用を想像できる人にしか書けません。AI駆動開発で人間が一番価値を出せるのは、たぶんここです。
まとめ:層が上がるたびに、同じ話を繰り返してきた
人間は信用の基準を書き、AIは実装を書き、検証は別の目で行う。これが、AIのコードを機械語と同じように扱うための条件です。
振り返ると、あの1万行はこの条件をひとつも満たしていませんでした。決定性だけを頼りに信用していた。動いたのは偶然です。AIの出力はもともと偶然の産物で、私はその偶然を確かめないまま固めて出荷しました。
機械語を疑わずに済むのは、どこかで誰かが疑ってくれているからです。AIが書いた自分のプロジェクトのコードには、その誰かがいません。疑う役は、人でもテストでも別のAIでもいいから、自分で置くしかありません。
- 第3言語(指示と仕様)は人間が書いて、リポジトリに残す
- AIが埋めた仕様の穴は、人間が決めたテストで固定する
- セキュリティは、機能テストとは別のゲートと別の目で見る
- その下の層は、世界の検証を信用する
1950年代にFORTRANが出たとき、アセンブラを手で書いていたプログラマーの多くは「機械が書いたコードは手書きにかなわない」と疑いました。開発チームを率いたJohn Backusは、手書きの半分の速さしか出なければ受け入れられないと考え、最適化するコンパイラを作ることで答えました。信用は説得ではなく、仕組みで勝ち取られたわけです。クラウドが出たときは「触れないサーバーは信用できない」でした。層が一段上がるたびに、同じ抵抗と、同じ決着がありました。
AIのコードは、その何回目かです。読めることに価値を置いてきた人ほど戸惑うかもしれません。でも問うべきは「読むかどうか」ではなく、「何が揃っていれば信用できるか」です。
あなたのチームなら、AIの書いたコードをコンパイラが出した機械語と同じように扱うために、何が揃っていれば信用できますか。
参考
- Ken Thompson, “Reflections on Trusting Trust“, Communications of the ACM 27(8), 1984
- Kent Beck, “Augmented Coding: Beyond the Vibes” / The Pragmatic Engineer「TDD, AI agents and coding with Kent Beck」(2025)
- John Backus, “The History of FORTRAN I, II, and III” (1978)

コメント