コードレビュー用のプロンプトでは、何を探すか、各指摘がどれくらい深刻か、どんな証拠を示すかをモデルに伝えるべきです。「このコードをレビューして」では、docstring の助言と命名の好みのあいだにセキュリティホールが挟まった、感じのいい提案の一覧が返ってきます。チェックリスト、深刻度、差分の依頼を含むレビュー用のプロンプトなら、指摘を1つずつ対応できるレポートが返ってきます。
「レビューして」から、深刻度つきのチェックリストへ
どちらのタブのコードにも本物のバグが2つあります。それぞれの返答がそれをどう示しているかを比べてください。
def get_user(conn, username):
cur = conn.cursor()
cur.execute(f"SELECT id, email FROM users WHERE name = '{username}'")
row = cur.fetchone()
return {"id": row[0], "email": row[1]}全体として、関数は短く読みやすいです。いくつか提案します。
- 型ヒントを追加する。たとえば
username: strとすれば、シグネチャがわかりやすくなります。 - docstring を追加する。関数が何を返すかを説明します。
- パラメータ化クエリを使う。f 文字列で SQL を組み立てると SQL インジェクションにつながる可能性があります。
- ユーザーがいない場合を処理する。
fetchone()はNoneを返すことがあります。 - 命名:
fetch_userのほうがデータベースへのアクセスをうまく表すかもしれません。
1つ目の返答も2つのバグを見つけています。そしてそこが、何に触れているかでレビューを評価することの問題点です。インジェクションは5項目の3番目に置かれ、「つながる可能性があります」とやわらげられ、修正方法もなく、docstring の助言と並んでいます。ざっと読んだ人は型ヒントを足して先に進むでしょう。
2つ目のプロンプトは4つのことを変えました。
- 範囲。 「バグ、セキュリティ、エラー処理だけ。スタイルは飛ばして」でノイズがなくなります。スタイルはリンターとフォーマッターの仕事で、そちらのほうが速く、自分と食い違うこともありません。
- 深刻度。 モデルに順位を付けさせ、順位の付いた一覧はどこから手を付けるべきかを教えてくれます。
- 問題を引き起こす入力。 「
x' OR '1'='1」は、あいまいなリスクを実行できるデモに変えます。最後のセクションで見るように、誤検知をふるい落とす最良のフィルターにもなります。 - 差分。 指摘ごとの小さな差分は、1つずつ適用するか却下するかを決められます。書き直された関数は、すべての修正と誰も頼んでいない変更を混ぜてしまいます。
「ある深刻度に該当するものがなければ、何かをでっち上げないで」は、見た目以上に重要です。レビューを頼まれたモデルは、見つけるものがあまりなくても指摘を出しがちで、最も弱い指摘が一覧を水増しします。ある深刻度について何も報告しなくてよいとはっきり許せば、その水増しが減ります。
レビュアーに必要なコンテキスト
人間のレビュアーは、コードが何のためのもので、入力がどこから来るかを知っています。モデルが知っているのは、あなたが貼り付けたものだけです。「username はログインフォームからそのまま来ます」という一文が、SQL インジェクションを理論上の話ではなく深刻度の高い指摘にしています。レビューに役立つコンテキストは次のとおりです。
- 入力がどこから来るか:ユーザー、別の社内サービス、自分たちが管理する設定ファイル。
- 何がそのコードを呼び出しているかと、何が返ってくることを期待しているか。
- 実行環境:言語のバージョン、フレームワーク、データベースのドライバ。上のインジェクションの修正で
?を使っているのは、sqlite3 がそのプレースホルダーを使うからで、ほかのドライバには%sを使うものもあります。 - この変更で最も重視すること:正しさ、セキュリティ、パフォーマンス、またはその3つすべて。
再利用できるレビュー用のプロンプト
このブロックは、チェックリストをテンプレートにしたものです。言語、重点、コードを変え、ルールはそのまま残してください。制約のパートをオフにすると、それがない場合にどれだけノイズが返ってくるかを手早く確かめられます。
async function loadAll(ids) {
const results = [];
ids.forEach(async (id) => {
const res = await fetch(`/api/items/${id}`);
results.push(await res.json());
});
return results;
}1. 高:リクエストが1つも終わらないうちに関数が戻る(3行目)
forEach は async のコールバックを待ちません。loadAll は空の results 配列をすぐに返し、push はそのあとで行われます。呼び出し元は必ず [] を受け取ります。
2. 中:失敗したリクエストがデータとして扱われる(5行目)
404 や 500 のレスポンスは、fetch では例外になりません。JSON のエラー本文はパースされて項目であるかのように push され、JSON でない本文なら、誰も await していないコールバックの中で res.json() が例外を投げます。
両方をまとめて直す修正で、結果の順番も ids と同じに保てます。
async function loadAll(ids) {
- const results = [];
- ids.forEach(async (id) => {
- const res = await fetch(`/api/items/${id}`);
- results.push(await res.json());
- });
- return results;
+ return Promise.all(
+ ids.map(async (id) => {
+ const res = await fetch(`/api/items/${id}`);
+ if (!res.ok) throw new Error(`Item ${id}: HTTP ${res.status}`);
+ return res.json();
+ })
+ );
}
役割の行は基準(「本当の問題だけ、それ以外は報告しない」)を決めており、ロールプロンプティングが最も役立つのはこういうところです。プルリクエストに指摘をコメントとして投稿するスクリプトのように、レビューをプログラムが読むなら、文章ではなく決まったフィールドを持つ JSON を頼んでください。方法は構造化出力で紹介しています。
誤検知への対処
間違った指摘もあります。モデルには、入力を検証している呼び出し元も、あるケースを起こりえなくしているデータベースの制約も、奇妙に見える行が意図的である理由も見えません。ライブラリの動作について間違えることもあります。自信ありげな主張が確認済みの主張ではない理由は AI のハルシネーションで説明しています。
各指摘は、それを引き起こす入力で試してください。あなたのシステムでその問題に到達する入力を作れないなら、修正を受け入れるのではなく、足りないコンテキストを示して反論してください。
function formatPrice(cents) {
return `$${(cents / 100).toFixed(2)}`;
}
この関数は `renderCart` からしか呼ばれておらず、`renderCart` は `Number.isInteger(cents)` が真でなければ早めに return します。それでもまだ問題ですか?問題でなければ、指摘を取り下げてください。そのチェックがあるなら、問題ではありません。formatPrice が受け取るのは常に整数なので、この指摘は当てはまらず、取り下げます。
1点、そのチェックでカバーされないことがあります。負の整数は Number.isInteger を通過し、formatPrice(-500) は "$-5.00" を返します。返金や割引がこの関数に届く可能性があるなら、代わりに "-$5.00" にしたいかもしれません。届かないなら、変更は不要です。
新しい証拠を見せられたモデルは、指摘を取り下げるか、その証拠を踏まえてもなお当てはまる理由を説明するかのどちらかをすべきです。どちらも役に立ちます。どんな反論にも同意するだけのモデルはレビューをしていないので、同意したかどうかではなく、理由で返答を判断してください。モデル自身が書いたコードは新しい会話でレビューしてください。そうすれば、コードを生み出したのと同じ推論を通してレビューが読まれずに済みます。範囲、コンテキスト、テストという同じ習慣は、そもそもコードを良くするのにも役立ちます。コードを書くためのプロンプトを参照してください。
よくある質問
コードレビューに良いプロンプトとは?
何についてレビューするか(バグ、セキュリティ、エラー処理)を示し、各指摘に深刻度を付けさせ、問題を引き起こす具体的な入力と、それを直す差分を求めます。頼まない限りフォーマットと命名は飛ばすよう伝えてください。そして人間のレビュアーが持っているコンテキスト、つまりそのコードが何のためのもので、何から呼ばれているかを伝えてください。
AI は人間のコードレビューの代わりになりますか?
なりませんが、最初の一次チェックとしては役立ちます。速く、処理されていない None、await の書き忘れ、文字列で組み立てた SQL のような、よくあるバグのパターンを見つけるのが得意です。プロダクトのルールも、コードベースの残りの部分も、ある判断が下された理由も知らず、指摘の一部は間違っています。人間のレビューの代わりではなく、その前に使ってください。
AI のコードレビューが実在しない問題を報告するのはなぜですか?
モデルはあなたが貼り付けたコードしか見ていないからです。値が呼び出し元で検証されていたり、あるケースがあなたのシステムでは決して起きなかったりしても、モデルにはそれがわからないので、とにかくリスクとして指摘します。問題を起こす具体的な入力を求めれば、その多くをふるい落とせます。問題を引き起こす現実的な入力がない指摘は、たいてい誤検知です。
レビューのときに AI にコードを書き直させるべきですか?
ファイルを書き直させるのではなく、指摘ごとに1つずつ小さな差分を頼んでください。全面的な書き直しは、修正と誰も頼んでいない変更を混ぜてしまい、その書き直しもレビューしなければならなくなります。差分なら、指摘ごとに受け入れるか却下するかを決められます。