生成されたコードは動いていて、テストも通っている。
それでも、なぜその形になったのかを説明できない。
そういう状態に、レビューをしていて出くわすことがあります。
気になったのは、この状態がどこから生まれるのかでした。
この記事は、AI が書いたコードに対して「分かったつもり」がどう起きるのかを、自分のレビュー経験から整理したものです。
忙しい人のための要約
明日のレビューで何を変えるかを決めるだけなら、ここまでで足ります。
- 「分かったつもり」は、動いているという結果が理解の証拠として代用されたときに起きる
- 一番隠れるのは、スタイルを揃えただけに見えるのに中で条件分岐が変わっていて、動いてしまうので気づけない差分
- 100%説明できる状態にしておくべきなのは全行の記憶ではなく、なぜこうしたのかという設計意図のほう
- 読む密度を上げる順番は「気づけるか」で決める
「なぜこうしてるんですか?」に答えられない
目撃した場面
プルリクエストをレビューしていて、実装の意図が読み取れない箇所に出くわすことがあります。
詰めるつもりはなく、単純に「これはなぜこうしてるんですか」と確認しただけです。
返ってきたのは、分かっていない、という答えでした。
同じ場面に、一度ならず出会っています。
自分も同じだった
これは他人事として書ける話ではありません。
自分も、あまり明るくない技術スタックで開発したときは同じ状態でした。
生成されたコードを読んで、動いていることを確認して、それで理解した気になっていた。
だからこの記事は、誰かのレビューを責める話ではなく、自分に問い直すための整理として書きます。
「動いている」は理解の代わりにならない
説明できなかった側に悪気があるわけではないと思っています。
テストは通っていて、画面も想定どおり動いていた。
その状態で「理解できていない」と自覚するほうが、むしろ難しい。
「分かったつもり」の正体は、動いているという結果が、理解の証拠として代用されてしまうことにあります。
動作は結果であって、なぜその実装になったかの説明ではありません。
同じことは以前の記事でも書きました。
AI が書いたコード自体は動く。
ただ「なぜこう書いたのか」を自分が説明できないと、レビュアーの指摘に的確に応えられず、そこで手戻りが発生します。
未知のレガシーシステムをClaude Codeで開発して学んだこと
100%説明できるべきなのは、設計意図のほう
では「理解している」とはどの状態を指すのか。
前提として、プロダクトコードは普通に読みます。
AI が生成したから読まなくていい、という運用は取っていません。
そのうえで、100%説明できる状態にしておくべき対象は、全行を目で追い切ることではありません。
なぜこの実装にしたのか、という設計意図のほうです。
さきほどの記事で「自分が100%理解するまで実装に進まない」と書きましたが、これを撤回するつもりはありません。
理解の対象が違うだけです。
型定義や import のような、フォーマッタが整える範囲の定型は、サラッと目を通せば意図まで説明できます。
一方で分岐条件やデータの扱いは、行数が少なくても中身に踏み込まないと説明できません。
どちらの粒度でも通らない答えが「AIが書いたので分かりません」です。
動くのに気づけない差分
設計意図を説明できる状態を保とうとするとき、実際に足をすくわれるのはどこか。
書き方を揃えられた差分の中身
実際に遭遇したのは、書き方が古かった箇所を新しい書き方に揃えられ、しかもその過程で条件分岐のロジックが変わっていた、というものでした。
案件のコードはそのまま出せないので、同じ構造を最小の例で再現します。
↓ 元のコード
if (user.isActive && user.role !== "guest") {
return renderDashboard(user);
}
return renderLogin();
↓ 新しい書き方に揃えられたあと
const canView = user.isActive || user.role !== "guest";
return canView ? renderDashboard(user) : renderLogin();
早期returnを三項演算子に畳んで、条件に名前を付けただけの整形に見えます。
ところが && が || に変わっていて、休止中のアカウントでもダッシュボードが表示されるようになりました。
なぜ見逃すのか
差分の見た目がスタイル統一なので、レビューの注意そのものが下がります。
書き方を揃えただけの箇所を、一行ずつ読み直す人は多くありません。
そして、この変更は動きます。
有効なアカウントで触るかぎり期待どおりに表示されますし、テストが分岐を網羅していなければ緑のままです。
前節の「動いているは理解の代わりにならない」が、そのまま効いてくる形です。
意図しない差分は捨てて、別issueに切る
見つけたあとの扱いは決めています。
頼んでいない変更は、良し悪しを判断する前に今のPRから外します。
この差分も捨てました。
書き方を新しく揃えること自体は妥当でも、そのPRで解こうとしていた課題とは無関係だからです。
内容がよければ採用することもあります。
ただし今のPRには混ぜず、別issueを切って独立させます。
レビューの単位と変更の単位がずれたままだと、次に同じ形の差分を見たときも同じ見落とし方をするからです。
どこに腰を据えるか
こういうことが起きるから読む、というのがここまでの話でした。
とはいえ、すべての差分に同じ密度は割けません。
優先順位をどう決めるか。
「気づけるか」を主役にする
見ているのは、だいたいこの3つです。
| 見るところ | かみくだくと |
|---|---|
| 間違いやすさ | AIが手を滑らせやすい種類のコードか |
| 壊れたときの痛さ | 間違っていたら、どれくらい困るか |
| 気づけるか | 間違っていたとき、あとから自分で気づけるか |
主役に置いているのは3つ目の「気づけるか」です。
検知性、と呼んでいます。
前の2つは、こちらの都合では動かせません。
けれど「気づけるか」だけは、どこをどれくらい読むかを変えるだけで自分で上げられます。
動かせない2つは、気づけなさが同じくらいの箇所が並んだときに、どちらを先に読むかを決める材料にします。
その意味では、コードのハルシネーション(存在しないAPIやメソッドを書いてしまうこと)は比較的安全なミスに入ります。
実行すれば即座にエラーになるので、気づけないまま先へ進むことがありません。
AIレビューに任せても検知性が上がらない領域がある
では、その「気づけるか」はAIレビューで底上げできるのか。
別途 /code-review の effort を low と high で3回ずつ回して測ったことがあります。
たとえば、存在をチェックしてから書き込むまでの間に別の処理が割り込んで、確認したはずの前提が崩れるバグ(check-then-act の競合状態)は、人間の目でも見つけにくい、気づけなさの代表格です。
これが6回とも一度も検出されませんでした。
指摘してくる場所が正しくても、添えられた説明まで正しいとは限りませんでした。
どこまで信じるかは、結局こちらで判断するしかありません。
検証の詳細はこちらにまとめています。
Claude Code /code-review の effort は low で足りる?low/high を3回ずつ実測した検出率の差
見切れていないなら、まずここから
腰を据えて読むと決めているのは次の4つです。
- データを壊す操作(DELETE / UPDATE / マイグレーション / ファイル削除)
- 外部との境界(API呼び出し・認証認可・入力値バリデーション・環境変数)
- 条件分岐と例外処理(分岐条件・try/catch での握り潰し・早期return)
- 自分が指示していないのに増えたもの(頼んでいない依存追加・設定変更・広げられたリファクタ範囲)
前の3つは、間違っていたときに気づく手段が乏しい場所です。
4つ目が、ここまで書いてきた差分の話にあたります。
これは「ほかは読み飛ばしていい」という話ではありません。
プロダクトコードは読む、が前提です。
今の時間の使い方では見切れていないと感じたときに、まずどこから密度を上げるかという順番として書いています。
まとめ
理解しているかどうかを、動いているかどうかで測らない。
なぜこうしたのかを自分の言葉で説明できるかで測って、時間が足りないなら、間違っても自分では気づけない場所から密度を上げる。
スタイルを揃えただけに見える差分の中で条件分岐が変わっていた経験がある身としては、生成コードを読まずに通す運用を取る気にはなれません。
AI駆動開発の進め方そのものは書籍にまとめました。
Xをフォローいただけると嬉しいです!
AI駆動開発(特に Claude Code)のノウハウや Tips をよく発信しています!

