AI Codingについては、使える技術はどんどん使えばいいと思っている。実装の速度も試せる案の数も、人間だけでコードを書いていた頃とは変わった。数時間かけていた作業が短時間で進むこともある。

一方で、生成されるコードが増えた分だけ、人間も同じ速度でレビューできるようになるわけではない。「AIが大量に書くなら、人間も大量に読む」で回し続けるのは難しいと思う。

試作を三つ作って比較できるのはいいことだが、三つとも本番へ入れるなら、変更の影響を確認する仕事も三つ分になる。実装の速度が上がったことと、採用してよい変更を判断できる速度が上がったことは同じではない。この差を、レビュー担当者の頑張りだけで埋めるつもりはない。

そこで重要になるのは、レビューをやめることではなく、人間がコードを逐一確認することへの依存度を下げても、品質を維持できる構造を作ることだと考えている。

先に仕様と責務の境界を決める

AIに実装を任せるとしても、何を作るかを曖昧にしたままでは検証のしようがない。システムをどう分けるか、各コンポーネントは何を担当するか、データはどこからどこへ流れるか。正常な状態と異常な状態の境界も、先に決めておきたい。

例えば「キューを作る」だけでは、同じメッセージが二度配信されてもよいのか、失敗した処理をいつ再試行するのか、順序を保証するのかが分からない。実装が動いて見えても、正しいかどうかを判断できない。実装方法をAIに委ねることと、判断の基準まで暗黙に委ねることは別だ。

仮に「失敗したメッセージは再試行する」と決めても、無限に再試行するのか、回数を超えたら別の場所へ送るのかで運用は変わる。処理がタイムアウトした場合、相手側では成功している可能性もある。そこを決めずに実装だけ進めると、あとから見つかった重複配信をバグと呼ぶべきか、仕様どおりと呼ぶべきかさえ曖昧になる。

最初に欲しいのは長大な設計書ではない。入出力の約束、責務の境界、失敗時の扱い、変更してはいけない既存の振る舞いが分かることだ。図でも、短い文章でも、具体的な例でもよい。人間とAIが同じ前提を参照でき、後から検証できる形になっているかを重視したい。

仕様を最初から完全に書けるわけではない。作りながら曖昧さが見つかることもある。ただ、そのときに「どの前提を変更したか」を明示して、テストや境界の定義へ戻す。AIが書いたコードを後から眺めて仕様を推測する状態にはしたくない。

ここでシステムを分割しておく意味も大きい。一つの変更が認証、保存形式、画面表示のすべてにまたがるなら、AIが書いた差分を読み切る以前に、何を守るべきか分からなくなる。境界が明確なら、各部分に渡す値と返す値、許容する副作用を限定できる。人間が全体像を持ったまま、細部の実装を任せやすくなる。

レビューする量を増やすだけでは追いつかない

コードレビューには価値がある。設計に反していないか、読み手にとって保守しやすいか、テストが見ていない条件を落としていないか。人間が読んで初めて気づくこともある。

ただ、AI Codingで実装の供給量が増えるほど、すべての行を同じ密度で読む運用はボトルネックになる。レビュー待ちを解消するために読む時間を削れば、今度は見落としが増える。人間の集中力を無限に増やすことはできない。

しかも、差分を読んだことと、動作を理解したことは一致しない。数行の変更でも、呼び出し元や永続化されたデータとの関係まで見ないと判断できない場合がある。反対に、型が明確でテストも揃った局所的な変更なら、生成された行を一行ずつ追うより、契約と動作を確かめる方が有効なこともある。レビュー時間を行数だけで配分するのは、あまりよい基準ではないと思う。

だから、コードレビューの相対的な位置付けは変わると思う。品質保証をレビュー一つに背負わせず、機械で確かめられることは先に確かめる。人間のレビューは、仕様の解釈や責務の境界、セキュリティ上の判断など、機械的な検査だけでは決めにくいところへ集中させたい。

変更の危険度も一律ではない。文言の修正と、認証やデータ削除の処理を同じ手順で通す必要はない。後者には人間の確認を厚く残し、必要なら実行権限やデプロイ権限も別にする。「人間が全行を読まない」と「人間が責任を持たない」は違う。

特に、権限の境界、金銭や個人情報の扱い、データ移行、外部へ公開するAPIの変更は、一度間違えると戻すのが難しい。こういう箇所では差分の読み合わせだけでなく、変更前後の振る舞い、失敗時に戻せるか、誰が承認するかまで含めて考える。人間の判断を省く場所と残す場所を分けるためにも、変更の影響範囲を小さく保つ設計が効いてくる。

コード以外もレビューの対象になる

レビューという言葉を、生成された差分を読む作業だけに限定しない方がいいとも思う。最初に決めた仕様が実際の問題に合っているか。テストがその仕様から重要な条件を取り出せているか。出来上がった機能が期待した作業を助けるか。コードを読む前後に確認する対象はほかにもある。

例えば、AIが提案したテストケースを人間が見るなら、「このassertは実装の結果をそのまま写していないか」「失敗時の期待値が決まっているか」を気にする。コードにバグがあっても、そのバグと同じ動作を期待値に書けばテストは通る。実装を一行ずつ追う代わりに、テストの判定基準を疑う方が価値のある場面もある。

仕様のレビューでも、書いてあることだけではなく、書かれていない境界を見る。正常な入力では何が起きるかだけでなく、同時実行、途中失敗、再実行ではどうなるか。すべてを事前に列挙するのは無理だが、危険な境界を見つけて質問を立てるのは人間が時間を使うべきところだと思う。

つまり、人間のレビュー時間を減らしたいというより、レビュー時間を何に使うかを変えたい。コードの行数に比例して読むより、間違えたときの影響と、機械では確定しにくい判断に合わせて配分する。そのためにも、機械で確かめられる部分を先に整備しておく。

レビュー以外の検証を重ねる

コードを読む量を減らすなら、代わりに何で間違いを検出するかを用意する必要がある。型チェック、Linter、静的解析、単体テスト、結合テスト、E2Eテスト、セキュリティスキャン、Property-Based Testing、Fuzzing。役割の違う検査をCIで組み合わせる。

例えば単体テストが一つの関数の分岐を見ていても、コンポーネント間のデータの受け渡しは保証しない。E2Eテストが一つの正常な操作を通しても、境界値や例外系がすべて正しいことにはならない。複数の層で、違う種類の失敗を拾う必要がある。

キューの例なら、単体テストで再試行回数を確認し、結合テストで保存先とのやり取りを確かめる。それでも、プロセスを途中で止めたあと再起動するとどうなるかは、別に試さないと分からない。大量の入力や壊れた入力を与えるテストは、通常の成功例とは違う問題を見つける。何を怖がっているのかによって、必要な検査は変わる。

テストケースの数を増やすこと自体も目的ではない。正常系だけを何百件通しても、重複処理を防げるかは分からない。逆に、守るべき性質が「同じ操作を再実行しても結果が増えない」なら、それを直接確かめる方が意味がある。仕様から性質を取り出してテストにし、実装の内部構造とは別の方向から確認したい。

AIにコードレビューをさせたり、仕様との整合性を確認させたりするのも使える手段だと思う。複数のモデルで見比べることもできる。ただ、生成したコードと生成したテストが同じ誤解に基づいていれば、両方が揃って通ってしまう。AIによる相互レビューも独立した正しさの証明にはならない。可能なら、仕様から決めた期待値や既存の振る舞い、外部から観測できる性質を検証の基準にしたい。

例えばAIへ「失敗した操作を安全に再試行できるようにして」と渡し、同じAIに実装とテストの両方を作らせたとする。AIが「タイムアウトなら処理は行われていない」と思い込めば、実装もテストもその前提で揃ってしまう。緑色のCIは、その前提が正しかった証拠にはならない。何を正解とするかという問題は、コード生成とは独立して残る。

「テストが通った」という結果だけでなく、そのテストがどの約束を確認していて、何を確認していないかを把握する。ここは結局、人間が問題を理解していないと決められない部分だ。

機械的に確定することをLLMに聞かない

AIを使う範囲を増やすことと、すべてをLLMに解かせることも違う。

フォーマットが崩れているか、型が合うか、依存関係に既知の問題があるか。こうしたことは既存のツールで検査したほうが速く、安く、再現性も高い。正規表現やパーサーで確定できることを、毎回LLMの判断へ戻す必要はない。

ここで言う「機械で解決する」は、何でも新しいAIの仕組みに置き換えることではない。コンパイラに型を調べさせ、パーサーに形式を調べさせ、テストランナーに入出力を比較させる。それぞれ得意なことをさせれば、同じ入力に対して同じ条件で失敗を再現できる。失敗した理由も、あとから追いやすい。

さらに、入力や出力の形式を型やスキーマで固定し、許容しない状態を作りにくくすることもできる。実行時に起き得る問題を、設計やビルド時の制約へ移す。LLMを使わずに済む判断を増やせるなら、そのほうが運用も安定すると思う。

例えば境界を越えるデータに検証済みの型を使えば、その先の処理で毎回「この値は本当に許されるか」と推測する必要が減る。失敗を曖昧な既定値へ置き換えず、明示的なエラーとして扱うこともできる。これはAIの能力を上げる話ではなく、AIが実装しても間違いにくく、間違えたら検出しやすい形へシステムを寄せる話だ。

LLMが向いているのは、曖昧な要件を整理したり、複数の実装案を出したり、ログとコードを行き来しながら調査の仮説を立てたりする場面だろう。ただし、その仮説が当たっているかは、できる限り実行結果や明確な検査で確かめる。

変更を小さくして、止めたり戻したりできるようにする

検証を通っても、実際に使われるまで分からない問題はある。そのときに、変更が一度に大きく入り、すぐ全利用者へ広がり、元へ戻せない状態では困る。AI Codingで変更を速く作れるようになるなら、変更を投入する側にも歯止めが要ると思う。

機能単位で分けて入れる、古い保存形式を読む期間を設ける、切り替えを段階的に行う、必要なら機能を止められるようにする。こうした手段が使えるかはシステムによるが、「失敗したとき何を戻せるか」は実装前に考えておきたい。データを書き換える処理なら、単に旧バージョンのコードへ戻すだけでは元の状態に戻らないこともある。

レビューを薄くできるかどうかは、テストの多さだけでは決まらない。影響範囲が小さく、異常を検知でき、取り消せる変更の方が、人間が判断しやすい。逆に、一度の実行で大量のデータを消す処理は、たとえコードが短くても慎重に扱う必要がある。

問題が起きたときに追えるようにする

どれだけ検証を重ねても、問題がゼロにはならない。AIが実装したコードが増えるなら、発生後に調べられる状態を作ることがいっそう重要になる。

構造化ログ、エラーの文脈、リクエストID、トレース、メトリクス、デプロイ履歴。AIがどの変更を加え、どのテストを実行したかも、あとからたどれる形で残したい。障害のときに「最近何か変えた」から始めるのと、失敗したリクエストがどの版のどの処理を通ったか分かるのとでは、調査の進み方が違う。

例えば、再試行が増えたときに、ログに「失敗しました」とだけ残っていても、原因は絞れない。どのメッセージが、どの処理段階で、何回目の試行として失敗したか。そこに直近のデプロイを重ねて見られれば、入力の問題なのか、外部サービスの障害なのか、変更による退行なのかを調べ始められる。調査のために毎回コード全体を読んで推測する必要も減る。

AIの作業履歴も同じだと思う。「AIが直した」という事実だけでは足りない。どの問題を直すつもりで、どのファイルを変え、何を実行して成功と判断したのか。差分と検証結果が結び付いていれば、あとから別の人がその判断を点検できる。会話履歴を無制限に保存するというより、採用した判断の根拠を残す方が重要だ。

観測性は予防策の代わりではない。ログを増やしてもバグはなくならないし、機密情報をむやみに記録するわけにもいかない。それでも、実装を全員が暗記していなくても、現象から該当する層へ到達できる設計は必要だ。

実装を暗記するより、動作原理を理解したい

AI Codingでは、自分が直接書いていないコードが増える。個々の実装を細部まで覚え続けるのは難しいし、それだけを目標にする必要もないと思う。

一方で、問題が何で、なぜ起き、どの層を疑うべきかを考える力は手放せない。データベースの障害を調べるとき、ORMの全実装を暗記している必要はない。しかし、トランザクションやロック、分離レベル、インデックス、クエリプランを知らなければ、遅延や不整合の意味を取り違える。

これはデータベースに限らない。メモリ管理、並行処理、ネットワークの再試行、ファイルシステムへの書き込みなど、ソフトウェアが何を前提に動いているかを知っておきたい。ライブラリの呼び出し方だけを覚えていても、前提が崩れたときに何が起きるかは分からない。

自分はバグに遭遇したら、直ったという結果だけで終わらせたくない。観測できた症状から、関連する処理がどう動くはずなのかを確認し、どの条件でその前提が崩れたかを理解するようにしている。再現条件を絞り、仮説に合うテストを作り、修正前後で結果を比べる。原因を完全に証明できないこともあるが、その場合は観測した事実と推測を分けて残す。

例えばBoaのGC参照保持漏れでは、最初は特定の環境でプロセスが落ちる問題として見えていた。しかし、実際に調べるべきだったのは、どの値がいつまで必要で、GCが何を生きていると判断するかだった。症状が表に出た場所と、原因のある場所は同じとは限らない。

修正自体は小さくても、その変更がなぜ必要なのか、どの条件でバグが発生し、テストが何を防いでいるのかを理解したい。同じ症状が別の場所で出たときに、前と同じ修正を当てはめるのではなく、動作原理から改めて原因を考えられるようにしておきたい。

もちろん、未知のバグに遭遇したとき、最初から自分だけで原理を説明できるわけではない。AIと一緒にログやコードを追い、仮説を出し合うこともある。知らない仕組みについては、教員に質問するようにAIへ説明を求める。「なぜそう動くのか」「その説明が正しいなら、どんな条件で再現するのか」と聞きながら、自分の理解を進める。

ただし、教わった説明をそのまま原因として採用はしない。実際のコード、仕様、再現テストと照らし合わせて確かめる。AIと分析することと、判断をAIへ丸投げすることは違うと思っている。

基礎を知っていれば、AIへ質問するときにも「ここは排他制御の問題ではないか」「クエリプランは変更前後でどう違うか」と、調べるべき点を絞れる。説明を読むだけでなく、ログや実行計画で裏付けを取る方向にも進める。逆に前提が分からなければ、もっともらしい修正がなぜ危険なのかにも気づきにくい。

AIが実装を助けるほど、コンピューターサイエンスの基礎や、そのシステム固有の動作原理を理解する重要性はむしろ上がるのではないか。AIの説明がもっともらしく見えても、前提が違えば結論も違う。その違いに気づくには、コードを読む量とは別の知識が要る。少なくとも問題が起きた箇所については、「この修正でテストが通った」だけでなく、なぜそのバグが発生し、なぜその修正で防げるのかまで説明できる状態にしたい。

使いたいものかどうかは人間が判断する

もう一つ、仕様やテストだけでは決めきれないことがある。出来上がったものを、そもそも自分や利用者が使いたいかどうかだ。

要求どおりに動き、テストも通っているのに、操作が面倒だったり、欲しかったものと微妙に違ったりすることはある。これは実装の正しさとは別の評価になる。AIに案を出させたり、使いにくそうな箇所を指摘させたりはできるが、実際に触ってどう感じるか、利用者の時間を使う価値があるかは、人間が確かめて判断しなければならない。

例えば、入力項目がすべて保存され、ボタンを押せば正しい結果が返る画面でも、日常の作業で毎回同じ項目を入力し直すなら使いづらい。機能単位のテストは通る。画面も仕様書どおりかもしれない。それでも、利用者が本当にやりたかった作業が楽になっているとは限らない。

その判断には、実際の作業を試してみる必要がある。どの操作で迷うか、どこで待たされるか、既存の手順より本当に楽か。数字で見られる部分もあるが、測る項目の選び方自体に判断が要る。AIに代案を出させることはできても、何を面倒と感じるか、何を大事にしたいかまで自動で決まるわけではない。

AI Codingで作れる量が増えるなら、作れたものを全部そのまま採用するのではなく、使ってみて、いらないものをやめる判断も必要になる。何を作るかを決めるだけでなく、出来上がったものが本当に欲しかったものかを確認するところまで、人間の仕事だと思う。

まとめ: 人間の責務はどこに残るか

AI Codingで実装を任せる範囲が広がっても、人間の責務が消えるわけではない。まず、誰のどんな問題を解くのかを決める。システムの責務を分け、何を正しい動作とするか、どの失敗を許容しないかを定義する。これが曖昧なら、AIが速くコードを書いても、その成果を評価する基準がない。

次に、その基準をどう確かめるかを用意する。型や静的解析で落とせるものは落とし、テストでは仕様上重要な性質を確かめる。人間によるレビューは、すべての行を同じ密度で読むためではなく、仕様の解釈、危険な境界、テストの判定基準といった判断が必要なところへ向ける。AIが書いたテストが通ったというだけで、そのテストの期待値まで正しいと考えない。

それでも問題は起きる。そのとき現象を追えるようにし、変更を止めたり戻したりできる状態を作ることも人間の責務だと思う。コードを読まなくてよいようにするというより、問題が起きたら必要な箇所へたどり着けるようにしておく。さらに、基礎原理と実際の動作を踏まえ、バグがどう発生したかを理解してから、AIが出した説明や修正案を評価したい。

そして最後は、出来上がったものを使う価値があるかを判断する。仕様どおりに動いても、使いにくければ作り直す。試作をたくさん作れるようになったからこそ、採用しない、機能を減らす、方向を変えるという判断も重要になる。

自分は、AIが書いたコードを一行残らず把握することを目標にはしない。その代わり、何を作り、どう正しさを確かめ、そもそも使いたいものになっているかを判断できる状態を保ちたい。問題が起きたら、どういう原理で動いていて、どこで前提が崩れたのかを理解する。コードをすべて読まなくてもシステムを制御できる仕組みを作り、その仕組みを理解して使うことが、これからの人間の仕事だと思う。