はじめに
山一情報システム ビジネスデベロップメント部の hoshi です。
AIのコードレビューはどの構成が良いのか(単独か、サブエージェントに分けるか)を検証するために、バグが混ざったコードを用意して、構成ごとの検出結果を比べていました。
その採点の途中で、5体のレビューを集約したレポートが「問題なし」と明示した箇所に、バグが残っていることに気づきました。しかも5体の誰ひとり、そのバグに気づいていませんでした。
当初の目的は構成の比較でしたが、この「問題なし」の正体を追いかけた方が収穫が大きかったので、実測の結果ごと共有します。全構成 Claude Opus 5 です。
測った構成
サブエージェントを何体か立ち上げて「君はバックエンド、君はフロント、君はテスト」と観点を割り当てる組み方をしていました。人間のレビュー体制の真似です。これが効いているのか確かめるために、次の構成を同じコードベースの独立コピーに対して走らせました。
単独(サブエージェントなし)
1セッション。サブエージェントを立ち上げず、
そのセッション自身がコードベース全体を読んで報告する。
5体・観点あり
1セッション。メインがサブエージェント5体を起動し、観点を割り当てる。
sub1: バックエンド API ルート層
sub2: データモデル・スキーマ層
sub3: フロントエンド
sub4: 仕様突合(業務要件整合)
sub5: テストと防御の不在
最後にメインが5体の報告を集約して1本のレポートにする。
検証のやり方
レビューさせたのは、社員が購入申請を出し、承認者が承認する。部署ごとに月の予算上限がある——という題材の検証用アプリです(自前コード約2,900行・53ファイル)。このコードには、事前の検証で存在を把握しているバグが複数入っています。各構成に同じものの独立コピーを渡しました。
採点は、探させたのとは別のAIセッションが行い、報告に挙がった指摘をソースと突き合わせて実在を確認しています。実在を確認できた指摘だけを数えることで、でっち上げや水増しが入らないようにしています。また「バグを12件見つけました」という自己申告の件数は、まとめ方の粗さが構成ごとに違うため使っていません。本稿に載せた事実は、最終的に人が生ログとソースで確認しています。
起きたこと: 「問題なし」と言われた箇所に、バグが残っていた
5体・観点ありの見落としのなかに、以下の2つのバグがありました。
- 承認記録の二重化: 1件の申請を2人が同時に承認すると、承認の記録が2行残る(本来は1行)
- 予算のすり抜け: 予算の残りが少ないとき、2人が同時に申請すると両方通ってしまい、部署の予算上限を超える
どちらも、1人ずつ順番に操作している限りは正しく動きます。 壊れるのは、複数の人が同時に操作したときだけです。以下ではこの2件を「並行系のバグ」と呼びます。どちらも実在が確認済みのバグです。
見落としただけなら、まだ分かります。気になったのはその先です。2件のうち承認記録の側について、集約レポートは該当箇所を確認したうえで「各遷移で1行ずつ記録され、順序も正しい」と、「問題なし」の側に明示的に載せていました。 順番に動かした場合だけを確かめて「仕様どおり」と書いたわけで、同時に動かしたらどうなるかは誰も試していません。
つまり「問題なし」の実態は、AIが確かめた範囲では問題なしでした。厄介なのは、その「確かめた範囲」がレポートに書かれていなかったことです。レポートには「問題なし」という結論だけが載っていて、読んだだけでは、同時実行を試していないことに気づけませんでした。
では、なぜ5体のうち誰も同時実行を試さなかったのか。見落とされた2件は、どちらも同時に操作したときだけ壊れるバグです。ここで気づきました。並行性を確かめろとは、メインにもサブ5体にも、どこにも書いていません。 5観点は層で切った素直なリストのつもりでしたが、どれも「いま在るものが正しいか」を単一の操作について問う書き方で、同時に操作したらどうなるかを問う指示は、どの観点にもありませんでした。つまりこの2件は、5体の誰の担当でもなかったことになります。
この見落としが生まれた原因は、2通り考えられます。担当を区切ったこと自体が主因なら、区切りを外して全員に全体を見させれば、検出されやすくなると予想できます。指示文に並行性への言及が無いことが主因なら、区切りを外すだけでは変わらず、言及を足した条件で検出されやすくなるはずです。
原因の切り分け: 今回は「指示文で触れていたか」で分かれた
この2つを切り分けるため、指示文に「並行・順序・同時の操作で壊れないかも疑うこと。」という趣旨の一文を入れるかどうかと、構成の組み方を変えた組み合わせを走らせて比べました。観点なしの5体には、観点を割り当てるかわりに「担当は決めない。コードベース全体からバグを全部探せ」とだけ指示しています。
| 並行を疑う一文 | 構成 | 予算のすり抜け | 承認記録の二重化 |
|---|---|---|---|
| なし | 単独(3本) | 見落とした(3本とも) | 見落とした(3本とも) |
| なし | 5体・観点あり | 見落とした | 見落とした |
| なし | 5体・観点なし | 見落とした | 見落とした |
| あり | 単独(3本) | 見つけた(3本とも) | 見つけた(3本中2本) |
| あり | 5体・観点あり | 見つけた | 見落とした |
| あり | 5体・観点なし | 見つけた | 見つけた |
一文がない構成は、どちらのバグも1件も見つけられませんでした。見つけた構成はすべて一文ありです。 5体に増やしても、観点を割り当てても外しても、この結果は変わりませんでした。少なくとも今回の試行では、見落としを観点の分担では説明できなさそうです。
ただし、一文を足せば必ず見つかる、というわけでもありませんでした。 観点ありの5体は、この一文で予算のすり抜けを見つけた一方、承認記録の二重化は見落としています。今回の範囲では、書かなければ見つからず、書いても必ず見つかるとは限らない、という結果でした。
見つけた側の動きには共通点がありました。一文があると、自分で並行アクセスを再現するコードを書いて走らせてバグを確認していました。一文がないと、今回は誰もそれをやっていません。
観点リストの働きも見えました。一文なしの5体どうしで比べると、観点を外した側は、拾えた指摘の幅が狭くなりました。 重大バグ数件の深掘りに収束し、観点ありの5体が観点リスト(境界値・制約・状態遷移の隅)に導かれて拾っていた細かい種類の指摘をしなくなったのです。観点リストは、書いてある範囲を拾わせる足場としては機能していたようです。気をつけるべきはリストそのものではなく、リストの外が「問題なし」に混ざって返ってくることでした。
実務でどう組むか(今回の結果から)
| やること | 理由 |
|---|---|
| サブエージェントを増やす前に、指示文を見直す | 5体に増やしても指示文に無いものは補われず、単独でも指示文にあれば見つかった。ただし足せば必ず見つかるわけではない |
| 観点リストは外さず、足りない次元を足す | 一文なしの条件では、観点を外すと拾える指摘の幅が狭くなった |
| 「問題なし」には、何をどう確かめたかも書かせる | 事前に書けなかった観点は、確かめた範囲の記述から漏れとして拾うのが現実的 |
結果を読むうえでの注意
本稿の結果は「この条件・この試行数での観測」です。一般化はできません。
- 各条件おおむね1〜3試行です。 一文の有無どおりに割れたのが偶然の一致である可能性は排除できません
- 並行バグの再現は、テスト用DB(SQLite)上の擬似的な並行実行を含みます。 行ロックのあるDBでは再現率が変わる可能性があります(チェックと更新が分かれている構造自体は残ります)
- 題材は小さいアプリ1本です。1本のセッションでも全部読める規模で、実務規模のコードベースでは結果が変わる可能性があります
- モデルは Opus 5 単一です
おわりに
今回の検証では、エージェントを増やしても見落としたバグが、指示文に一文を足すと見つかりました。少なくとも今回の試行では、エージェントの数や担当分けといった構成の組み方よりも、指示文に並行性への言及があるかどうかのほうが、検出結果ときれいに対応していました。観点リストも、書いた範囲を拾わせる道具としては働いていました。ただし、怖いのは確かめていない範囲まで「問題なし」と読める形で返ってくることです。そのため、「問題なし」には確かめた内容も書かせたうえで、そのレポートを読んで、確かめ方が足りない箇所がないかを判断することが大切だと思います。
指示文に何を書くか、返ってきた「問題なし」をどう読むか。この2つを意識するだけでも、AIレビューとの付き合い方は変わってくるように思います。同じような構成を組んでいる方の参考になれば幸いです。
最後まで読んでいただきありがとうございました。