AIによるコードレビューでより良いコード品質を提供する方法を考える

AI

こんにちは。GMOグローバルサイン・ホールディングスの城戸(@sutobu000)です。
普段はビジュアルシステムの研究開発やインハウスでの映像制作・配信を担当しています。

コードレビューは、ソフトウェア開発において品質を保証するための重要なプロセスです。しかし、多くの組織ではレビューに費やす時間や人的リソースの確保に課題を抱えています。
本記事では、AIを活用したコードレビューの可能性について、オライリー出版の「リーダブルコード」の原則を踏まえながら探っていきます。具体的には、AIと人間それぞれの強みを活かしたハイブリッドアプローチを提案し、ClaudeとGeminiを使った実例も紹介します。これにより、コードレビューの効率化と品質向上を同時に実現する方法を解説していきます。

コードレビューとは

コードレビューとは、開発者が書いたコードを他の開発者が検査し、フィードバックを提供するプロセスです。
主なレビュー方法には以下のようなものがあります

  • ペアプログラミング:リアルタイムでのレビュー
  • フォーマルレビュー:計画的な会議形式のレビュー
  • プルリクエストベース:GitHubなどで行う非同期レビュー
  • ツール支援レビュー:静的解析ツールを活用したレビュー

これらのレビュー方法のフローとしては大きな差はありませんが、基本的なフローは以下の通りです

  1. コード変更の提出
  2. レビュアーによる確認
  3. フィードバックの提供
  4. 修正と再レビュー
  5. 承認と取り込み

他レビュー方法と比較するとペアプログラミングに関してはこのようなフローを行っている実感はないと思いますが、指示するナビゲーターとコードを記述するドライバーで行うもので、上記のフローを基に紐どいていくと、指示によりコードを記述し、ナビゲーターが確認しフィードバックも同時に行い、修正と再度レビューして承認するというようなフローをリアルタイムで行っているものと考えられます。

このプロセスを通じて、コードの品質向上と知識共有が図られています。

コードレビューの価値

複数メンバーによる開発においてコードレビューは必要不可欠なものになっています。
一人が読みづらいコードを書いてしまうだけでも、全体に影響を及ぼしかねないものになる恐れがあります。
そのような場合にコードレビューを行うことで、コード全体の品質が保たれ、コーディング知識の共有などによりチーム全体の向上にもつながっていきます。

良いコードレビューには次のような価値があります

  • コード品質の向上:バグの早期発見、パフォーマンス改善
  • 知識の共有とチーム学習:暗黙知の共有、技術向上
  • コーディング標準の維持:一貫性のあるコードベース
  • チーム文化の醸成と帰属意識:協力的な開発環境
  • 技術的負債の蓄積防止:長期的なメンテナンス性の向上

コードレビューの課題

コードレビューを行うことで様々なメリットがありましたが、メリットだけではありません。

実際にレビューを導入する際には、以下のような課題が生じることがあります

  • レビューに費やす時間:貴重な開発リソースの消費
  • レビュー品質のばらつき:レビュアーの経験や知識による差
  • フィードバックの遅延:開発サイクルの長期化
  • 主観的な判断と感情的摩擦:チーム間の不和
  • レビュアーの確保難:特に専門知識が必要な場合

特にレビュー時間は貴重な開発リソースを消費するため、効率化の余地が大きいといえます。大規模な開発になればなるほどコードレビューに費やされるリソースは比例して増大していくものと思います。

コードレビューを専門とする人がいれば話は変わりますが、多くの場合は開発を共にするチームメンバー間でレビューを分担しています。これにより、本来のコード開発時間が圧迫され、プロジェクトの進行速度に影響することも少なくありません。

リーダブルコードの観点

今回はオライリーから出版されている「リーダブルコード」の観点を踏まえて今回のAIによるコードレビューに当てはめてみたいと思います。

リーダブルコードが提唱するコードの原則は以下の通りです。

「コードは他の人が最短期間で理解できるように書かなければならない」 – リーダブルコード

複数メンバーによる開発においてコードが最短期間で理解できるようなものであることは、本質的な目的を表している原則だと思います。

コードをレビューするためにはそのコードを理解することが必然になります。
そのためリーダブルコードの原則のように、最短で理解できるようなコードを求めることはコードレビューにおいても大事な要素であると考えられます。

リーダブルコードの核は「理解するまでにかかる時間」を短くすることにあります。
単にコードが短いことより理解しやすいことを優先し、「他の意味と間違えられることはないか」を常に考えることが重要です。

こうした基準を活用する価値として

  • 共通言語としての活用(「これはリーダブルコードの〇〇だね」)
  • デザインパターンのカタログ的な考え方
  • チーム全体のコード品質向上

これらの原則は、AIによるコードレビューを考える上でも重要な指針となります。

AIによるコードレビューの可能性

コードレビューの課題としてレビューに費やされるリソースの確保などが主になります。
その課題をAIを使って解決し、コードレビューの効率化を計れる可能性を考えてみます。

AIによるレビュアーが特に得意とするもの

AIは人間が処理するには時間がかかる単純作業などを短時間で処理することができるのがメリットと思います。
現にデータをまとめる際などにはAIを通してデータの整理を行ったり、日常業務などでも活用されていると思います。

今回のコードレビューにおいても同じように活用はできるものと考えています。

AIは以下の点で特に効果を発揮します

  • パターン認識と一貫性チェック:コーディング規約への準拠
  • 大量のコードの高速処理:人間よりも多くのコードを短時間で分析
  • ベストプラクティスとの比較:既知のパターンとの照合
  • 24時間365日稼働可能:時間的制約のない対応

パターン認識と一貫性チェック、大量コードの高速処理は大きなメリットと思います。
コードレビューの課題であった時間的リソースの確保の問題を大幅に解決できるものになり得ますね。

人間のレビュアーが得意とするもの

一方で、人間のレビュアーは以下の点で優れていると考えられます

  • コンテキストの理解:プロジェクト背景や開発経緯の理解
  • 意図の把握:コードの目的と実装の一致
  • ビジネス要件との整合性:機能的要件の満足度
  • 創造的な代替案の提示:より良い設計の提案

コンテキストの理解などAIでは考慮ができづらい内容については、人間によるコードレビューが必要になってくるものと思います。
もちろん進化し続けているAIが補ってくることも考えられますが、意図しないコードレビューが行われる可能性も考えられるため少なくとも人間によるレビューは必要になってくるのではないかと考えています。

AIと人間の協働によるコードレビュー

AIと人間によるコードレビューについてそれぞれの強みについてまとめてみましたが、AIによる時間的リソースの確保ができたりとメリットはありました。一方で人間じゃないと確認できないようなコンテキストの理解などはまだまだ人間によるコードレビューが必要になるという印象です。

どちらか一方だけでコードレビューをすることはまだ難しいという点から、AIと人間それぞれの強みを活かした協働モデルとして、以下のようなフローが考えてみました

  1. AIによる自動レビュー:基本的なエラーや一貫性の問題を自動チェック
  2. 開発者によるAIフィードバックへの対応:機械的な問題の修正
  3. 人間によるフォーカスを絞ったレビュー:コンテキストや意図の理解など
  4. AIモデルへのフィードバック:より精度の高い自動レビューへつなげる

どちらの強みも踏まえたコードレビューの協働モデルは、AIによる大量のコード高速処理や一貫性チェックといった、人間が時間を費やしてしまうものを対応してもらい、以降のコンテキストや要件の整合性など人間が確認するべき内容についてレビューするようなフローになってくるものと思います。
全体的なコード品質をAIでレビューし、整理されたコードを基に人間によるコードレビューを行うことでより品質の高いコードレビューを提供できるようになるのではないかと思います。

実際にAIによるコードレビューを試してみた

先の協働モデルを行うために、AIに先述したリーダブルコードの要約をナレッジとして登録し、AIによるコードレビューを試してみます。

今回は以下を利用しました。

Claude

簡単なコードレビューを試す方法に、Claudeには プロジェクト という機能があります。

プロジェクトは、Claude ProまたはClaude for Workプランのユーザーが、独自のチャット履歴とナレッジベースを持つ自己完結型のワークスペースを作成できるようにします。各プロジェクト内で、ドキュメントをアップロードし、コンテキストを提供し、Claudeと集中的なチャットを行うことができます

つまりは用意したドキュメントのコンテキストを提供して、それを基にチャットを行うことができるというものです。Claude Proプランが必要ではありますが、簡単な方法で行えるというところで今回はこちらを使用していきます。

Claudeのクライアントからプロジェクトを作成し、プロジェクトナレッジを設定します。

先ほどのリーダブルコードの要約を読み込んでもらい、こちらから提示するコードに対してレビューをしてもらうような指示を与えています。

簡単なコードで試してみます。
以下のコードは、HTMLドキュメント内の空の入力フィールドを検索するJavaScript関数です。具体的には「input1」、「input2」というように連番のIDを持つ入力要素を順番に調べ、最初に見つかった空の入力フィールドに新しい値を設定します。
シンプルですが、今回AIによるコードレビューの効果を確認するために、変数の使い方や制御フローに改善の余地があるものにしています。

var setFirstEmptyInput = function (new_value) {
    var found = false;
    var i = 1;
    var elem = document.getElementById('input' + i);
    while (elem !== null) {
        if (elem.value === '') {
            found = true;
            break;
        }
        i++;
        elem = document.getElementById('input' + i);
    }
    if (found) elem.value = new_value;
    return elem;
};

AIからは次のように返されます。


リーダブルコードの観点からのコードレビュー

提示されたコードをリーダブルコードの原則に沿ってレビューします。

コードの分析

このコードは空の入力欄を見つけて値を設定する関数です。ID が “input1”, “input2” などの命名パターンに従った要素を順番に検索しています。

改善ポイント

  1. 制御フロー変数の使用
    found 変数はリーダブルコードでいう「制御フロー変数」の典型例です。このような変数はコードの流れを追いにくくします。
  2. 重複コード
    elem = document.getElementById('input' + i) がループの外と中で2回出現しています。DRY(Don’t Repeat Yourself)原則に反しています。
  3. ブロック構造の不明瞭さ
    最後の if (found) が1行で書かれており、視覚的にブロック構造がわかりにくくなっています。
  4. 関数の目的と名前の不一致
    関数名 setFirstEmptyInput は「最初の空入力欄を設定する」ことを示していますが、関数は設定した要素を返しています。名前と振る舞いが完全に一致していません。

リファクタリング提案

var setFirstEmptyInput = function (new_value) { 
   for (var i = 1; true; i++) {
      var elem = document.getElementById('input' + i); 

      // 要素が見つからなければ検索終了
      if (elem === null) {
         return null; // 空の入力欄が見つからなかった
      }

      // 空の入力欄が見つかったら値を設定して返す
      if (elem.value === '') { 
         elem.value = new_value; 
         return elem; 
      }
   } 
};

改善の説明

  1. ガード節の適用: 早期リターンを使って制御フローをシンプルにしました
  2. 制御フロー変数の除去: found 変数を完全に除去しました
  3. whileループからforループへ: 変数の初期化とインクリメントを1か所にまとめました
  4. コードの重複を排除: 要素の検索を一か所にまとめました
  5. 適切なコメント: コードの意図を明確にするコメントを追加しました
  6. 改行と空白: 視覚的に処理の区分を示す空白行を入れました

追加の提案

関数名と戻り値の一貫性を高めるためには、次のいずれかの対応も検討できます

  1. 関数名を findAndSetFirstEmptyInput に変更して、要素を検索して設定する目的を明確にする
  2. または、設定が成功したかどうかのブール値を返す形に変更して、関数名との一致を高める

これらの改善により、「コードを理解するための時間」が短縮され、メンテナンス性が向上します。


上記のようにリーダブルコードの要約の観点に則ってレビューをしてくれました。
簡単なコードではありましたが、大量なコードでも同様な結果を得られました。

Gemini

GeminiにもClaudeのプロジェクトと同じような機能に Gem があります。

目的と設定に関するすべての情報を含めて Gem を作成するか、特定のアクション用に作られたプリメイド Gem を使用すれば、同じような指示で特定のトピックについて調べる際の手間を減らすことができます。独自のニーズに合わせて個別の Gem を作成し、それを再利用して Gemini アプリでのプロンプトと質問を簡略化できます。

ClaudeはProなど有料プランでないとプロジェクトという機能が使えないものでしたが、GeminiのGemは無償で利用することができますので、より簡単に試しやすいと思います。

Claudeのプロジェクトと同じようにGeminiのGemでも指示を設定します。

先ほどと同じように簡単なコードで試してみます。

以下のコードをチャットします。

var setFirstEmptyInput = function (new_value) {
    var found = false;
    var i = 1;
    var elem = document.getElementById('input' + i);
    while (elem !== null) {
        if (elem.value === '') {
            found = true;
            break;
        }
        i++;
        elem = document.getElementById('input' + i);
    }
    if (found) elem.value = new_value;
    return elem;
};

AIからは次のように返されます。


はい、承知いたしました。「リーダブルコード」の考え方に則って、提示されたJavaScriptのコードをレビューします。

このコードは、最初に値が空のinput要素を探し、そこにnew_valueを設定する関数のようですね。

レビューポイント

  1. 制御フロー変数の削除 (9章): found 変数は、ループ内で空のinput要素が見つかったかどうかを示す制御フロー変数として使われています。ループを途中で抜ける break 文と組み合わせることで、この変数を削除し、より直接的なコードにできます。

  2. 変数のスコープを縮める (9章): elem 変数は while ループの外でも宣言されていますが、ループ内でのみ使用されます。変数のスコープをループ内に限定することで、コードの可読性と保守性を向上させることができます。

  3. 早期リターンによるネストの削減 (7章): 空のinput要素が見つかった場合、すぐに値を設定して関数からリターンすることで、if (found) のブロックをなくし、ネストを浅くできます。また、要素が見つからなかった場合の処理も明確にできます。

  4. マジックナンバーの排除 (該当する場合): 'input' + i のような文字列結合は、input要素のID生成規則がコード中に直接書かれているため、変更があった場合に複数の箇所を修正する必要があります。もしこの規則が複数箇所で使われている場合は、定数として定義することを検討しても良いでしょう。

改善案

上記の点を踏まえ、コードを以下のように改善できます。

var setFirstEmptyInput = function (new_value) {
    var i = 1;
    while (true) {
        var elem = document.getElementById('input' + i);
        if (elem === null) {
            return null; // 見つからなかった場合を明示的に返す
        }
        if (elem.value === '') {
            elem.value = new_value;
            return elem;
        }
        i++;
    }
};

改善点

  • found 変数を削除し、ループ内で条件が満たされたらすぐに return するように変更しました。
  • elem 変数のスコープを while ループ内に限定しました。
  • 無限ループ (while (true)) を使用し、要素が見つからなかった場合は return null で明示的に示すようにしました。これにより、ループの終了条件がより明確になります。

この改善案は、元のコードよりも制御フローがシンプルになり、変数の役割が明確になるため、より理解しやすいコードになったと考えられます。

もし、input要素のID生成規則が複雑である場合や、他の場所でも使用されている場合は、その規則を定数として定義することを検討してみてください。例えば以下のようになります。

const INPUT_ID_PREFIX = 'input';

var setFirstEmptyInput = function (new_value) {
    var i = 1;
    while (true) {
        var elem = document.getElementById(INPUT_ID_PREFIX + i);
        if (elem === null) {
            return null;
        }
        if (elem.value === '') {
            elem.value = new_value;
            return elem;
        }
        i++;
    }
};

このようにすることで、ID生成規則の変更に強く、保守性の高いコードになります。

いかがでしょうか?このレビューが、よりリーダブルなコードを書くための一助となれば幸いです。他に気になる点や、さらにレビューしてほしいコードがあれば、遠慮なくお申し付けください。


GeminiでもClaudeと同じようにリーダブルコードの要約に則ってレビューしてくれていることがわかります。

ClaudeとGeminiの双方からアウトプットされたコードには、多少の違いはあるものの改善点はおおよそ同様に示されているものと思います。

このようにAIで一貫性やパターン認識を事前にチェックしてもらい、その後から人間によるコードレビューを行うというフローを実施することで、時間的リソースの確保ができるかつコードレビュー品質も上げられるものと思います。

まとめ

AIによるコードレビューは、人間のレビュアーを完全に置き換えるものではなく、むしろ補完するものとして考えるべきだと思います。
AIが機械的かつ一貫性のあるチェックを担当し、人間のレビュアーはより文脈に依存する部分に集中することで、コードレビューの効率と品質の両方を向上させることが期待できるものになっていくと思います。
特に技術標準化を進める上では、AIによるコードレビューは大きな助けとなると思います。新規事業の立ち上げ時や既存プロジェクトの移行時などに、一貫したコード品質を維持することが容易になってくるのではないかと思います。

実際に導入するステップとしては以下のようなフローになると思います。

  1. AIレビューツールの選定: GitHub Copilot, Claude, Geminiなど目的に合ったツールを選ぶ
  2. レビュー基準の設定: リーダブルコードなどの原則をAIに理解させる
  3. ハイブリッドワークフローの確立: AI→人間の二段階レビュープロセスを構築する
  4. 継続的な改善: AIのフィードバックを記録し、精度を向上させる

昨今ではGitHub Copilotによるコードレビューも提供されています。
プルリクエストしたコードに対してレビューしフィードバックをくれるといった機能もあり、導入もしやすくハードルを下げてくれているものになっています。

AIによるコード生成自体はまだ発展途上の面もありますが、コードレビューという限定的な用途では、AIの強みを最大限に活かすことができます。今後もAIの進化とともに、より高度なコードレビューが可能になり、開発プロセス全体の効率化が進むことを期待しています!