公開日 2026-04-22

AI生成コードのレビュー観点チェックリスト

品質ゲート通過後のAI生成コードに対して、人間レビューで確認したい観点を整理する。

目次

  1. 1. 前提と対象読者
  2. 2. 機械ゲートの次に人が見る範囲
  3. 3. 差分全体で最初に確認すること
  4. 4. 例外処理
  5. 5. 設定・環境変数・ログ
  6. 6. 権限・境界条件・副作用
  7. 7. すぐ使えるレビュー観点チェックリスト
  8. 8. まとめ

AI生成コードを受け入れる最小品質ゲート(PHPStan + PHPUnit + CS Fixer) のような機械ゲートを通した後でも、人間レビューで確認したい観点を整理する。

GitHub の PR 画面に限らず、ローカル差分レビュー、ペアレビュー、レビュー依頼前の自己確認でも使えるように、差分レビュー全般で流用しやすい形に寄せます。

題材は、問い合わせ管理画面に AI の返信下書き機能を足す PHP コードです。
DraftReplyServiceDraftReplyControllerDraftReplyPublisher という短い例を使いながら、どこを確認すると見落としを減らせるかを整理します。

この記事で扱わないもの:

  • GitHub のレビュー UI 操作
  • CI 構築や pre-commit 運用
  • AI モデル選定やプロンプト設計
  • フレームワーク別の詳細比較

生成段階の工夫ではなく、出てきた差分をどう読むかに絞ります。

1. 前提と対象読者

AI 生成コードのレビューで厄介なのは、「動きそうに見える」ことです。
整形が通り、型エラーもなく、簡単なテストも通ると、その先まで安心しやすくなります。

ただ、実務で事故になりやすいのは別のところです。

  • 失敗を握りつぶしていないか
  • 設定や環境変数を雑に読んでいないか
  • ログに残しすぎていないか
  • 権限チェックが抜けていないか
  • 空文字や再実行の扱いが雑ではないか
  • DB 更新やメール送信の順序が危なくないか

この記事の主眼は、品質ゲートを否定することではありません。
まず機械で落とせるものを落とし、その次に人間がどこを見るかを整理することです。

2. 機械ゲートの次に人が見る範囲

最初に、機械で落とす範囲と人が見る範囲を切り分けます。

先に機械で落としやすいもの人間レビューで残りやすいもの
整形崩れ例外時の扱いが妥当か
明白な型不整合設定の置き場が運用に合っているか
既知の振る舞い崩れログに残してよい情報だけが残っているか
単純な命名や記法の乱れ権限チェックや対象ユーザーが合っているか
既存テストで拾える退行境界条件や副作用が安全か

人がすべてを総当たりで読み切るしかない、という話ではありません。
見る順番を持っておくと、AI 生成コードでも確認漏れを減らしやすくなります。

タイトルどおりの簡易チェック項目にすると、最初に見るのは次の 5 点です。

  • 入口と出口は把握したか
  • 失敗は呼び出し側に見えるか
  • 設定はアプリの境界に集約されているか
  • ログに機微情報を出していないか
  • 権限・境界条件・副作用は安全か

以降の章では、この 5 点を順に掘り下げます。

3. 差分全体で最初に確認すること

個別のコードに入る前に、差分全体の入口と出口を先に見ます。
ここを飛ばすと、あとから各観点を見ても全体像がつかみにくくなります。

おすすめの確認順は次のとおりです。

順番確認すること先に見る理由
1どこから呼ばれる変更か管理画面なのか、バッチなのか、公開 API なのかで見る観点が変わるため
2何を読み、何を書き、何を送るかDB 更新、メール送信、外部 API 呼び出しの有無で事故の重さが変わるため
3追加された設定や環境依存はあるか手元では動いても、本番環境の設定不足や環境差分で例外になる変更を早めに見つけやすいため
4誰が実行できる処理か管理者だけなのか、担当者全員なのかで権限の見方が変わるため
5失敗時と再実行時にどうなるか途中成功や二重実行の事故を先回りして想像しやすいため

AI 生成コードでは、局所的な関数はそれらしく見えても、入口と出口が危ないことがあります。
たとえば「管理画面からしか呼ばれない想定で書いたコード」が、実際には API からも呼ばれるなら、権限やログの見え方は変わります。

個別の観点に入る前に、まずこの 5 点だけでも差分全体で押さえておくと、後の確認が楽になります。

4. 例外処理

例外処理でまず見るのは、catch の有無ではありません。
失敗が呼び出し側に見える設計になっているかです。

確認したい質問:

  • 失敗を握りつぶして成功扱いにしていないか
  • \Throwable を広く捕まえて、本当に必要な情報まで隠していないか
  • 失敗時に、呼び出し側が再試行や表示切替を判断できるか

短い例です。

<?php

declare(strict_types=1);

interface AiDraftClient
{
    public function createReply(string $inquiryBody): string;
}

final class DraftReplyService
{
    public function __construct(private AiDraftClient $client)
    {
    }

    public function build(string $inquiryBody): string
    {
        try {
            return trim($this->client->createReply($inquiryBody));
        } catch (\Throwable $e) {
            return '';
        }
    }
}

このコードは一見すると安全そうですが、失敗時に空文字を返しているため、呼び出し側から見ると「AI が空の下書きを返した」のか「通信や認証に失敗した」のかが分かりません。
レビューでは、空文字という値だけでなく、「失敗をどこで判断できるのか」を見ます。

この種のコードを見たら、少なくとも次の確認が必要です。

  • 例外を再送出すべきか
  • 失敗結果を型で表したほうがよいか
  • ログ、画面表示、再試行の責務をどこに置くか

AI 生成コードは「とりあえず呼べる」形を作るのは速い一方で、失敗時の契約が薄くなりやすいです。
たとえば「成功時は文字列、失敗時は例外」や「成功 / 失敗を表す結果型」のように失敗の表し方をチームでそろえておくと、AI 生成コードのレビュー基準もぶれにくくなります。

5. 設定・環境変数・ログ

次に見るのは、設定の置き場とログの粒度です。
設定とログは手元で動作確認しやすい分、雑に通りやすい観点でもあります。

確認したい質問:

  • 環境変数をアプリの境界でまとめず、メソッドの中で直読みしていないか
  • 運用で変わる値がコードに散っていないか
  • ログへ問い合わせ本文やメールアドレスをそのまま残していないか

短い例です。

<?php

declare(strict_types=1);

interface DraftReplyBuilder
{
    public function build(string $inquiryBody): string;
}

interface Logger
{
    public function info(string $message, array $context = []): void;
}

final class DraftReplyController
{
    public function __construct(
        private DraftReplyBuilder $builder,
        private Logger $logger,
    ) {
    }

    public function __invoke(array $request): array
    {
        $timeoutSeconds = (int) getenv('AI_TIMEOUT_SECONDS');
        $inquiryBody = (string) ($request['body'] ?? '');
        $customerEmail = (string) ($request['email'] ?? '');

        $this->logger->info('draft reply requested', [
            'timeout' => $timeoutSeconds,
            'inquiry_body' => $inquiryBody,
            'customer_email' => $customerEmail,
        ]);

        return [
            'draft' => $this->builder->build($inquiryBody),
        ];
    }
}

このコードでは getenv() をコントローラの中で直接読んでいます。
動くこと自体はありますが、設定の出どころが散りやすく、テストや環境差分の追跡もしづらくなります。

さらに、問い合わせ本文とメールアドレスをそのままログへ入れている点も見逃しにくいポイントです。
デバッグ中は便利でも、本番では不要に個人情報や機微情報を残す原因になりえます。

レビューでは次の切り分けが有効です。

  • 設定値はどこで束ねるべきか
  • ログは何を残せば調査に十分か
  • 機微情報を残す必要があるなら、保護と保持期間は妥当か

実務では、設定専用クラスや DI でアプリの境界へ集約しておくと、設定変更の責務とレビュー対象を追いやすくなります。
判断基準は「動いたからよい」ではなく、「運用に持ち込めるか」です。

6. 権限・境界条件・副作用

事故の大きさで言うと、この章の観点は特に優先度が高いです。
AI 生成コードは正常系を短く通すのが得意な一方で、誰が どの条件で 何回 実行できるかの整理が薄くなりがちです。

確認したい質問:

  • この処理を実行してよいユーザーだけが呼べるか
  • 空文字、重複実行、対象データ未存在のときに安全に止まるか
  • DB 更新とメール送信の順序は事故を起こしにくいか

短い例です。

<?php

declare(strict_types=1);

interface DraftReplyRepository
{
    public function savePublishedDraft(int $inquiryId, string $draft): void;

    public function isPublished(int $inquiryId): bool;
}

interface Mailer
{
    public function sendReply(int $inquiryId, string $draft): void;
}

final class User
{
    public function __construct(
        public readonly int $id,
        public readonly string $role,
    ) {
    }
}

final class DraftReplyPublisher
{
    public function __construct(
        private DraftReplyRepository $repository,
        private Mailer $mailer,
    ) {
    }

    public function publish(User $user, int $inquiryId, ?string $draft): void
    {
        $normalizedDraft = trim((string) $draft);

        if ($normalizedDraft === '') {
            return;
        }

        $this->repository->savePublishedDraft($inquiryId, $normalizedDraft);
        $this->mailer->sendReply($inquiryId, $normalizedDraft);
    }
}

このコードには、実務レビューで止まりやすい点がいくつかあります。

  • User を受け取っているのに権限確認がない
  • すでに送信済みかどうかを見ていない
  • 空文字を黙って無視していて、呼び出し側に理由が返らない
  • DB 更新後にメール送信が失敗したときの扱いが見えない

正常系だけ見ると短くまとまっていますが、本番では次の事故が起こりえます。

  • 権限のない担当者が送信確定できる
  • 二重クリックや再送で同じ返信が二重送信される
  • DB 上は送信済みなのに、メールだけ失敗して状態がずれる

レビューでは、DB トランザクション、送信済みフラグ、ジョブ化や outbox のように、途中失敗と再実行をどう扱うかのキーワードまで見ておくと判断しやすくなります。
DB 更新と通知送信が分かれる処理では、「途中失敗のあとに再実行しても二重送信や状態不整合が起きないか」を最低限の判断基準にすると、差分の危なさを整理しやすくなります。

7. すぐ使えるレビュー観点チェックリスト

最後に、ここまでの内容をそのまま使える形へまとめます。
差分レビューのたびにゼロから考え直さず、この表を入口にすると確認順を保ちやすくなります。

観点確認する質問見落とし例優先度
入口と出口この変更はどこから呼ばれ、何を読み、何を書き、何を送るか管理画面用のつもりで書いた処理が API からも呼ばれる
例外処理失敗は呼び出し側に見えるか。成功っぽい値に変えて隠していないか例外を握りつぶして空文字を返す
設定環境変数やタイムアウトはアプリの境界で管理されているかコントローラ内部で getenv() を直読みする
ログ調査に必要な情報だけを残しているか。本文や個人情報を出しすぎていないか問い合わせ本文やメールアドレスをそのまま記録する
権限この処理を実行してよいユーザーだけが通るかUser を受け取るだけで権限判定がない
境界条件空文字、未存在、重複実行、再送時の挙動は決まっているか空文字を黙って無視し、呼び出し側へ理由を返さない
副作用DB 更新、メール送信、外部 API 呼び出しの順序は安全か保存だけ成功し、通知が失敗して状態がずれる

この表は、記事の要約というより、レビュー時の手元メモとして使う想定です。
AI 生成コードを読むときは、まず 1 行目の入口と出口を見てから、優先度が高い行を順に確認すると進めやすくなります。

8. まとめ

AI 生成コードのレビューでは、品質ゲートを通ったあとに何を見るかを決めておくことが大切です。
整形、型、既知の振る舞い崩れを先に落とせるだけでも大きいですが、それで例外処理、設定、ログ、権限、境界条件、副作用まで自動で安全になるわけではありません。

人間レビューでは、まず入口と出口を押さえたうえで、次のような観点を見ます。

  • 失敗が見える設計か
  • 設定とログが運用に持ち込める形か
  • 誰が実行できる処理か
  • 境界条件と副作用が安全か

品質ゲートは、レビューの代わりではなく、レビュー対象を机に乗せる前処理です。
差分レビューでは、まず入口と出口を確認し、その後に例外処理、設定とログ、権限、境界条件、副作用の順で確認すると、AI 生成コードでも確認漏れを減らしやすくなります。