AI

プロジェクトの知見で育てる! 不具合検出特化型AIコードレビューSkillsとその運用

みなさん、AI時代でもコードレビューはしていますか?

わざわざ「AI時代でも」と頭につけたのは、AI駆動開発が一般的になった今、コードレビュー不要論が出てきていまして、社内でも話題に上がることが多いからです。

コードレビュー必要派は「最終判断は人間であるべき/コードレビューはコミュニケーションの場でもある」という趣旨のことを主張することが多いようです。

不要派は、「その人間の目こそ信用ならない/人間の判断は遅すぎる/AIと会話したほうが有益で楽しい」というニュアンスのことを仰っているようです。

AIの登場によって、自動テスト導入のコストなども下がりましたし、AIエージェントにテストをさせたりもできるので、コードレビューの位置づけが変わるのは確かだと思います。

今回は「人力コードレビューの要/不要」について結論を出さないのですが、この記事では私たちのチームで行っている、AIによるコードレビューやその他レビューサポートの取り組みを紹介したいと思います。

このプロジェクトではCursorを利用していますが、Claudeでも、GeminiのGemsでも似たような事はできます。


目次


AIコードレビューの課題

「人間のレビューが追いつかないならAIにやらせよう」というのは普通の発想だと思います。
AIに「レビューして!」と言うだけでやってくれますし、専用のSkillsも公開されているので、特別な手順を踏まなくてもそれなりのレビューはしてくれます。

ただ、私たちのチームにおいては、AIによるコードレビューを行う上で以下3つの課題がありました。

AIのレビューの指摘内容が安定しない/出力が毎回変わる

AIは与えられたプロンプト/コンテキスト/モデルに応じてアウトプットが大きく変わります。
重要な不具合を見つけてくれることもあれば、不具合をスルーしておだててくることもあります。同じプロンプトを打ち込んでもOK/NGが変わることがあります。

A「AIコードレビューで問題があったよ!」

B「え、同じプロンプト打ち込んだけど、俺のAIは問題なしと言ってたよ?」

ということも珍しくありません。

AIの指摘内容に対する対応方針がチームで統一されていない

AIの指摘内容は、致命的な不具合もあれば、可読性/保守性等の割と好み問題のようなものまで多岐に及びます。

そのような性質を持つので、レビュー対応要否の判断コストが高くなり、属人性も出てきてしまいます。

チーム開発においては、統一的な基準があるレビュープロセスが存在しないと安心感が生まれません。

この課題は「アウトプットが安定しない」という話にも通じるものがあります。

同じコードをレビューさせたとき、AさんとBさんで、違うアウトプットを受け取って、対応基準も異なってしまった場合、品質のばらつきが出てしまいます。

チーム独自のナレッジが反映されていない

チームには過去の学びの蓄積があります。

過去発生した不具合、レビューでよく話題になる観点、見逃しがちな点、ドメイン固有の仕様等のナレッジがあるわけです。

このような観点が反映されていないレビューは痒い所に手が届かないものになります。

私たちチームの解決策~バグチェック特化型Skillsの作成~

その課題を解決すべく、AIコードレビュー用のオリジナルのプロンプトを作り、AIが明示的に不具合と判定したものに限り、対応することにしました。

具体的には、コードレビュー用のSkills(Gems)を作りました。
その中で、チェックリスト形式でAIにレビューをさせ、「〇(不具合なし)、△(要確認)、×(明確に不具合)」で結果を返すようにし、×のみ対応を必須とすることをチームのルールとしています。

「修正ブランチとmainブランチとの差分」を自動抽出してくれるスクリプトをAIで作成し、それを活用してレビューさせることでレビュー範囲が不必要に広がることを防いでいます。

GeminiのGemsでもこちらは実行できるのですが、CursorなどのAIエージェントを活用すると、周辺コードも含め、立体的にレビューをしてくれます。

肝心の不具合チェックリストの中身は、過去発生した不具合や見逃しがちだった点をベースに作成し、過去の学びの蓄積を活かした形になっています。
チェックリストという方式は、人間がやるにはいろいろヘビーですが、AIは文句を言わずにやってくれます。

ちなみにこのプロセスを通すことで不具合がないことを完全に証明できるのかと言われれば、それは「できません」。
そのため、手動テスト、E2Eテスト、目視でのコードレビューも開発プロセス内には残してあります。

不具合検出特化コードレビュー用Skillsの具体的な中身

それで、できあがったプロンプト(Skills)の短縮版が以下です。

実際は300行以上あり、全文掲載はセキュリティ的にも問題がありますので、外部公開できる形にだいぶ加工はしました。
本来はファイルが分かれていたりする部分もまとめちゃっています。

ただ、テスト実行をしたところ、この短縮版でも不具合を抽出してくれたため、使える形にはなっていると思います。

プロンプト(外部公開版)

かなり長いので、下の「プロンプト全文(外部公開用抜粋版)」の行をクリックすると折りたためます。読み飛ばしたい方はご利用ください。

プロンプト全文(外部公開用抜粋版)

渡されたソースコード(差分)を、次の手順でバグチェックしなさい。

手順

  1. 先に重点観点で差分全体を俯瞰する
  2. 続けてチェックリスト27件を1項目ずつ判定する(スキップ禁止)
  3. 判定は「確信度(〇/△/×)× 深刻度(致命的/重大/軽微)」の2軸。混ぜない
  4. 詳細には × と △ だけを出す。〇は出さない
  5. 確信度の高い × を深刻度順・少数精鋭で挙げる
  6. 無関係な項目は個別に△にせず、末尾で「対象外: #…(理由)」と1行にまとめる

重点観点

  • 権限と出力:許可範囲外の参照・更新がないか。画面/API出力が適切に扱われているか
  • 例外・ログ・null:想定内の失敗をシステムエラー扱いにしていないか。広いcatchやnull参照はないか
  • オープン推論:データ無し/想定外の順序/再実行/隣接機能との組み合わせで破綻しないか

判定基準

  • 〇:問題なし
  • △:要確認。変更行に具体的な根拠があるときだけ。確認手順まで書く。書けないなら挙げない
  • ×:不具合あり。修正方針と修正前後コードを書く

深刻度:

  • 致命的:情報漏えい・権限逸脱・データ破壊・金額や権利の不整合
  • 重大:特定条件で機能破壊・誤表示/誤保存・過負荷になりうる
  • 軽微:UX・保守性・ログノイズ

指摘の書き方

各 ×/△ は次を含めること。

  • 一文サマリ:「〜の画面で、〜のとき、〜すると、〜が起きる」
  • 条件 / 影響(誰がどう困るか) / 原因 / 直し方(×)または確認手順(△)
  • ファイル名と行番号

出力フォーマット

📊 チェック結果サマリ
合計: N項目
  ✅ 〇: XX件
  ⚠️ △: XX件(致命的: X / 重大: X / 軽微: X)
  ❌ ×: XX件(致命的: X / 重大: X / 軽微: X)
  ⏭️ 対象外: #…(理由)

詳細は深刻度順(致命的→重大→軽微)、同深刻度内は × 優先。

チェックリスト(27項目)(外部公開用抜粋版)

該当コードが差分に無い項目は無理に深掘りせず対象外へ。

  1. バッチやファイル一括処理で、タイムアウト・メモリ・分割実行の方針があるか。捕捉できない実行時間制限も考慮されているか
  2. N+1問題は発生していないか
  3. 誤字脱字、タイポ、表記ゆれはないか(コメント含む)
  4. 存在しないキー等へのアクセスで実行時エラーにならないか
  5. 型の整合は取れているか
  6. デバッグ用コードが残っていないか
  7. 定数や選択肢の変更後、古い値を持つ過去データの表示フォールバックはあるか
  8. ループ内で使う判定・集計変数は、ループ先頭で初期化されているか
  9. 更新/取得のカラムとパラメータ、取得データと出力の対応に取り違えはないか
  10. 日付・時刻の境界値(開始/終了ちょうど)でも意図通り動くか。単発と期間の両方を考慮しているか
  11. 更新直後に同一データをすぐ再取得していないか(参照遅延を踏まえているか)。該当が無ければ対象外
  12. 一括更新/削除の対象範囲の絞り込みは十分か(無関係データを巻き込まないか)
  13. ボタン連打などによる二重送信が考慮されているか
  14. 「データなし(null)」と「0 / 空文字」が意図通り区別されているか
  15. バリデーションや例外時の戻りで、画面側が必要とする変数やメッセージ階層が欠けていないか
  16. 共通関数・共通設定の変更が、改修対象外の画面に悪影響しないか
  17. 新しいステータス/種別が、一覧・検索・CSV・バッチなど集計系にも波及しているか
  18. ログインユーザーの権限範囲内だけを参照・更新しているか。リクエストの識別子をうのみにして範囲外を扱っていないか
  19. ユーザー入力やDB値が、画面やスクリプトへ未エスケープのまま出ていないか
  20. 外部から来る値を数値・区分・日付などとして使う前に、型・範囲・許可値の検証があるか。異常値で処理が暴走・例外化しないか
  21. 未ログイン、該当なし、重複など想定内の失敗を、システムエラーや過剰なエラーログにしていないか
  22. 広い catch で、想定内失敗と本物の不具合を同一処理にまとめていないか。エラー区分の取り違えはないか
  23. null になり得る値を検証せず使っていないか。成功時と失敗時で戻り値の型が変わり、呼び出し側が壊れないか
  24. 外部APIの失敗・タイムアウト・「該当なし」「重複」などで、後続処理や通知が破綻しないか
  25. オープン推論(最重要):1〜24に無い不整合・仕様矛盾・使い勝手の問題を、次の3方向でそれぞれ深掘りする。定型消化で終わらせないこと
    • 25-A 違和感:読んで「変では」と感じた点を言語化する(状態遷移の抜け、再実行、処理順依存、端数、0件/最大件、部分失敗、キャッシュ乖離など)。後続処理や別画面の立場でも辿る
    • 25-B リスト外の軸:このリストが見ていない方向を見る(運用・後方互換・データ移行・件数増での急激な劣化・機能同士の相互作用・機微情報の混入など)。既にカバー済みの再チェックに工数を溶かさない
    • 25-C 業務として正しいか:動いても目的を果たしているか。当然必要な波及が差分に無いか。暗黙の業務ルールや要件の曖昧さも指摘する
    • 発生条件・確認手順・業務影響まで書けるものだけ挙げる
  26. 集計・ページ送りなどで、そろっているはずの件数や対応関係がコード上崩れていないか。机上で「必ずこうなる」と言い切れるものだけ指摘する(原則 ×)。指摘時は①前提 ②最小データ ③たどった経路 ④誰がどう困るか、を書く。推測は挙げない
  27. ループが終わらない経路や、入力・件数に依存して現実的でない回数だけ回る経路がないか。入力検証自体は #20、件数の重複・欠落は #26

プロンプトはここまでです。上の「プロンプト全文(外部公開用抜粋版)」の行をクリックすると折りたためます。

結果サンプル

上のプロンプトを流したときの出力例です。こちらも下の「AIの出力例」の行をクリックすると折りたためます。

AIの出力例
📊 チェック結果サマリ
━━━━━━━━━━━━━━━━━━━━
合計: 27項目
  ✅ 〇: 20件
  ⚠️ △: 2件(致命的: 0 / 重大: 1 / 軽微: 1)
  ❌ ×: 1件(致命的: 0 / 重大: 1 / 軽微: 0)
  ⏭️ 対象外: #1, #11, #18, #19 など(理由: 今回の差分は「水やりリマインダーのまとめて送信」のみ。画像アップロード・課金・地図等の変更は含まない)

❌ 不具合あり (1件)

  • 🟠 [重大] 元気な鉢がない棚が、リマインダー一覧から丸ごと消える(項目26)
    • 📁 app/Services/WateringReminderService.php (L52-67)
    • 一文サマリ:棚の鉢がすべて枯れていると、その棚自体が対象一覧から消え、「今日は水やり不要」の通知すら届かなくなる。
    • 🔁 どんな時に:棚の鉢が「枯れて archived になったミント」だけになっているユーザーへ、朝の一括リマインダーを送るとき。
    • 💥 何が起きる:その棚が通知にも一覧にも現れず、ユーザーは棚の存在ごと見失う。棚の総数も実際と合わなくなる。
    • 🧠 なぜ:archived 除外を LEFT JOIN の相手テーブル(pots)側の WHERE に置いているため、鉢が1つも残らない棚は NULL 行ごと弾かれ、LEFT JOIN が実質 INNER JOIN として動くため。
    • 🔧 直し方:除外条件を JOIN の ON 側に移し、対象の鉢が残らない棚も行として残るようにする。
// Before(イメージ)
->leftJoin('pots', 'pots.shelf_id', '=', 'shelves.id')
->where('pots.status', '!=', 'archived')

// After(イメージ)
->leftJoin('pots', function ($join) {
    $join->on('pots.shelf_id', '=', 'shelves.id')
         ->where('pots.status', '!=', 'archived');
})
  • 机上トレース:棚Aに鉢2つ(どちらも archived)→ 期待は棚Aが「対象なし」として残る → 現状は棚Aごと消える

⚠️ 要確認 (2件)

  • 🟠 [重大] 水やり間隔(日数)をキャストしておらず、変な値でループが膨らむ(項目20 / 項目27)
    • 📁 app/Http/Controllers/ReminderPreviewController.php (L28-36)
    • 一文サマリ:プレビュー用の days をそのまま for の上限に使うと、巨大値や文字列で処理が重くなる。
    • 🔁 どんな時に:プレビューAPIに days=999999days=banana を付けて呼んだとき。
    • 💥 何が起きうる:サーバーが長い空回しをして遅くなる。画面のスライダーからは起きにくいが、URLはいじれる。
    • ❓ 確認:整数化と上限(例: 1〜60日)があるか。
$days = max(1, min((int) $request->input('days', 7), 60));
  • 🟡 [軽微] 「今日は対象の鉢なし」をエラーログにしている(項目21)
    • 📁 app/Jobs/SendMorningWateringJob.php (L90-96)
    • 一文サマリ:雨の日設定などで対象0件は普通なのに、毎回 error で残ると監視がうるさい。
    • 🔁 どんな時に:全鉢が「今日はスキップ」になっている朝。
    • 💥 何が起きて困るか:アラート疲れで、本当の障害を見逃しやすくなる。
    • ❓ 確認:0件は info にしてジョブ成功扱いにできるか。

出力例はここまでです。上の「AIの出力例」の行をクリックすると折りたためます。

工夫ポイント

過去プロジェクトで発生した不具合/事象をベースにチェックリストを作成する

チェックリスト候補は以下のようにして広範囲から抽出して作成をしました。

  • Backlogから過去の不具合をCSVでエクスポート
  • 結合テスト仕様書の結果から不具合を抽出
  • その他、チームMTG等で見逃しがちな点をブレスト

抽出後はAIにテキストで渡して「誤字脱字を修正して、分類して、チェックリストにして!」と頼むだけです。

AIに整形してもらえるので、抽出時の形式や文章の誤字脱字等は気にしなくても良いです。CSVとテキストなど、形式が混ざっていても問題ありません。
そのため、抽出の形式より中身が圧倒的にポイントになります。良質な観点をいかに抽出できるかが勝負どころです。

チェックリストに記載のない観点も明示的に分析をしてもらう

チェックリストの弱点のひとつに「チェックリストにはない項目の確認が甘くなる」ことがあげられると思います。
また、チェックリスト方式にすることで、AIのパフォーマンスを制限しているのではないかという懸念もありました。
ガチガチにチェックリストを組むのはいいですが、それだとAIの自由な推論能力を阻害してしまいかねません。
そこで、このプロンプトにはチェックリストにはない観点を敢えて積極的にレビューするような箇所(その他)を組み込んでいます。

例えば、以下のような指示です。

  • ここまでのチェックリストには記載されていない不具合っぽいものを教えてください。
  • チェックリストには記載がない観点を中心に見てください。
  • そもそも要件レベルからズレていることを教えてください。

このような指示を与えることで、AIが自分で考える余地も残してあります。

「人間の指示を厳密に守らせる部分」「AIの知能の自由な発想を活かす部分」を分けている点がポイントです。

作成と実行はフロンティアモデルで行う

モデルの性能でバグの検出力が大きく変動するので、できるだけ高性能モデルを使うようにしています。
安価なモデルでNGなしだった場合でも、フロンティアモデルに変えたらNGだったことが何度もあります。
また、プロンプトの調整もフロンティアモデルを利用します。繰り返し使うものなので、レバレッジが効きます。

AIの指摘の対応有無を記録しておき、改善に活かす

AIコードレビューの出力とそれぞれの実際の対応有無はスプレッドシートに記録しています。
実績が一定以上溜まった後にそれをAIに分析させ、検出力の高い項目を特定したり、抜けている観点を洗い出したりしました。
作りっぱなしにならないようにし、実態に合わせて、より精度が高いものを作れるようにしてあります。

コードレビューの外にも広げていく

上記の通り、コードレビューから手を付け始めたのですが、この「過去の対応例等をAIに読み込ませ修正/レビューをさせる」という発想で、Skillsは複数作ってあります。

  • 要件定義レビュー用Skills
  • DBクエリチューニング用Skills
  • エラーログをベースとした不具合簡易修理Skills

すべてに共通しているのはAIに一般的な指摘をさせるだけではなく、プロジェクトで蓄積した過去の実例を読み込ませている点です。
ちなみに効果の傾向としては、チェックリスト方式のコードレビューやクエリチューニングといった正解不正解がはっきりしているものはAIがやはり得意で、要件定義のような抽象度が高いものは人間の判断がまだだいぶ必要になっています。

感想

このチェックリストに記載がある観点で、すり抜けてリリースされてしまった商用バグ等はありません。
本来手動でやらなければならない数十項目をAIのコマンド一発で流せるので工数削減効果も大きいです。
手動だと、チェックリストに1つ項目を追加する度に、工数も心理的負荷も跳ね上がってしまうものですが、AIだと気にならないです。
開発プロセス全体の定量的な分析はまだできていないのですが、これらの取り組みを始めてから工数削減/品質の向上ができている実感はあります。

個人的に気に入っているのが、プロセス自体がプロジェクトの経験に応じて賢くなっていく点です。
AIにチェック観点や出力例を追加するだけで進化するわけです。
AIがなかった時代だと、ナレッジなどがあっても、それを人間に共有し、継続して普段の業務に活かすのは至難の業でした。
しかし、この方式が洗練されれば、AIが学びを吸収していき、人間が意識せずとも過去の経験がアウトプットに反映されるので、学びの循環の難易度が激減するはずです。

今後の課題

今後の課題は自動化です。

現状は、過去の事例の抽出~Skillsに反映等はほぼ手動になっています。

これだと手間ですし、人間が気づいていない、捨てられていってしまっている学びもあるはずです。
人間が意識をしなくてもAIが自律的に判断し、自動でプロセスを進化させられるようにしたいです。
ナレッジの保管場所や実行結果等の配置場所を工夫してMCPで繋ぐことでうまいことできないかと日々夢想しています。

今後、AI駆動開発の進化によっておそらくこの形式もどんどん古くなって、新しいプロセスの構築が必要になってくると思います。
ただ、「人間の学びをAIにFBする」という大きい抽象的な流れが消えることはなかなかないと思いますので、考え方等を参考にしていただければ幸いです。