2025年、私たちはエージェンティック・コーディング(「バイブ・コーディング」という言葉はもう古いようです)の台頭を目にしました。AIアシスタントとエージェンティックなワークフローの間で、機能はこれまでに見たこともないようなスピードで生み出されています。企業が「自社のコードベースのXXパーセントは今や完全にAIによって書かれている」と自慢するのも珍しくありません。
これが良いことなのか悪いことなのかはまだわかりませんが(私は良いことだと思っています)、このコーディング速度の向上には代償も伴います。大量に生成されるコードのレビューは消耗する作業であり、コードレビューが急速にボトルネックになりつつあります。一部のチームやオープンソースプロジェクトでは、AIが生成したプルリクエスト(PR)を一切受け付けないという「核の選択」を採用しているところすらあります。
AIを禁止すれば一時的な猶予は得られるかもしれませんが、長期的には良い選択肢だとは思いません。「抵抗は無意味だ(Resistance is futile)」と、私のお気に入りの架空の種族なら言うでしょう。この新しいレベルの生産性を生き抜くためには、機械の方がうまくできる仕事を私たちがやるのをやめなければなりません。AIにはAIで対抗するのです。しかし、AIだけでなく、昔ながらの決定論的なツールも驚くほどの効果を発揮します。リンターが問題をキャッチできるなら、私が見る必要はありません。フォーマッターが自動的に修正できるなら、私は本当に気にする必要はないのです。
ここで私の「不人気な」意見を言わせてください。私は、コードを書いたのが人間だろうとエージェントだろうと気にしません。オープンソースの世界では、とにかくすべてがゼロトラストです。コードを書いたのが最先端のモデルだろうと、スリランカのティーンエイジャーだろうと、本当は関係ないはずです。理論上は、人間が生成したPRの方が小さくなるはずですが、この業界で20年ほど働いてきて、私自身も巨大なPRをたくさん経験してきました。だから、巨大でずさんなPRに対処するのは、決して新しい問題ではないと断言できます!
私はコードを額面通りに評価します。それは動くか?安全か?既知の問題を修正しているか?ロードマップと一致しているか?私たちの標準に準拠しているか?
そういうわけで、今日の記事では、外部からのコントリビューションに対処するときだけでなく、自分自身のAI生成コードに対処するときのコードレビューへのアプローチ方法について少し話したいと思います。実際のところ、AIを使ってコーディングするということは、AIを常にコードレビューしているということだからです。
私が本当に気にしていること#
最近コードをレビューするとき、私の視点はますます高レベルになっています。ある意味、手動でコードを書く量が減るほど、コードの個々の側面に気を使わなくなっています。私は、何らかの形でリーダーシップの役割を担ったすべてのチームで、常にこう言ってきました。「コードは使い捨てだ」。今日ほどこの言葉が真実味を帯びている時代はありません。繰り返します。コードは使い捨てです。使い捨てではないのは、特定のコードを開発したときに得た「システムの知識」です。この知識は、ある実装から別の実装へと移行するときや、たとえばAPIをv1からv2に移行するときに役立つものです。
2回目に書く方が簡単なのは、すでに多くのことを発見し、曖昧さの大部分を減らすという成長の痛みを経験しているからです。何がうまくいき、何がうまくいかなかったかを学んでいるからです。何が過剰設計(オーバーエンジニアリング)で、何が設計不足だったのかを。これこそがソフトウェアエンジニアリングにおける重要な部分です。知識を集め、反復し、進化させること。そして、これこそがAI時代を生き残る種類の知識なのです。コードは単なる実装の詳細にすぎません。
この哲学に基づき、私がコードレビューで気にしていることの(すべてを網羅しているわけではない)リストを以下に示します。
アーキテクチャとシステム設計#
AIモデルは全体像を把握するのが苦手で、また多くの近道を取る傾向があります。私のレビュープロセスでは、ハードコードされた値や設定、問題空間の過度な単純化(AIはコーディングの要求をプロトタイプやデモのように扱うことがよくあります)、そして逆説的ですが、過剰設計といったシグナルを探します。AIモデルには、本番環境への準備(production-readiness)=複雑さであると思い込む厄介な特徴もあります。言い換えれば、彼らはバランスと実用性の間で苦労しています。これらは私たちが経験から学ぶものであり、言葉に翻訳するのが難しいことが多い部分です。
パブリックAPIとモジュール#
私たちが構築しているもののエルゴノミクス(使い勝手)は重要です。パブリックAPIは、それを使用しなければならない平均的な開発者にとって「しっくりくる」ものである必要があります。うまく設計されたインターフェースは直感的で、誤用されにくく、コードベースの他の部分から厄介な内部構造を隠します。私はインターフェースが堅牢で、スコープが適切かどうかを確認し、表面積を可能な限り小さくすることを目指します。APIが使いにくければ、基礎となるコードがどれほどエレガントであっても意味がありません。コードは使いやすく、文書化されているか?パブリックAPIが優れているかどうかを示す良いヒントは、テストの品質が高いかどうかです。悪いAPI設計は、本質的にテストが困難です。
アルゴリズムとパターン#
LLMは多くの場合、問題を解決するために最も単純で力任せな(ブルートフォース)方法をデフォルトとします。バルクインサート戦略が正しいアプローチであるにもかかわらず、ネストされたループを使用し、数行ごとにコミットして大規模なデータ移行を実行しようとするエージェントはよく見かけます。あるいはもっとコアなレベルで、マップや辞書が適切なデータ構造であるときにリストを使用しているか?データ構造とアルゴリズムが実際の問題空間に適合していることを確認することで、パフォーマンスの劇的な低下を防ぐことができます。目標は、テストに合格するだけでなく、スケールするコードです。しかし、時期尚早な最適化は依然としてリスクです。よりシンプルなアプローチがわずかに遅くてもはるかに読みやすく、制限された小さなデータセットを扱っている場合は、通常は可読性が優先されます。
依存関係#
エージェントは、標準ライブラリなら3行のコードで解決できるようなタスクのために、巨大なサードパーティのライブラリを簡単に引っ張ってきます。すべての新しいパッケージは負債です。それは外部からのリスク、潜在的なセキュリティの欠陥、そして余分なメンテナンスのオーバーヘッドをもたらします。すべての追加は、積極的にメンテナンスされ、安全で、本当に必要なものでなければなりません。アプリを小さく保つことで、攻撃対象領域(アタックサーフェス)を減らすことができます。私たちのGenAI SDKや主要なWebフレームワークのようなコアツールは早くパスしますが、それ以外のものはすべて精査されます。少しのコピー(または再実装)は、少しの依存関係よりも優れています。コードの生成と保守が簡単になればなるほど、特にそれがコードベースに新しい攻撃ベクトルを追加することを意味する場合、再利用についての心配は少なくなります。
アンチパターンと品質の問題#
いくつか例を挙げると、神オブジェクト(god objects)、隠された状態の変更、副作用、グローバル状態、リソースリーク、エラーの無視、未使用の関数や変数などです。言語のイディオムも重要です。ある言語では罠のように見えるものが、別の言語では標準的なやり方であることもあります。私はこれらをとても気にしていますが、これらはgolangci-lint (Go) や ruff (Python) のような静的解析(リンター)を使用することで、最も簡単に自動化できるものの一部でもあります。
テスト容易性(Testability)#
テストが難しいコードは、通常、設計が悪く、将来の変更に抵抗します。関心事の明確な分離、クリーンな入力、純粋関数が理想的です。優れたテストはコードが機能することを証明し、将来の修正のためのセーフティネットを提供します。UIコンポーネントや複雑なシステムの場合、私は厳格なユニットカバレッジよりも実践的なテスト戦略を受け入れますが、コアロジックはカバーされていなければなりません。私はすべてのプロジェクトにカバレッジの目標を追加しようとするのをやめました。ケースバイケースだからです。しかし、テスト可能なものはすべてテストされていることを知る必要があります。理想的には、ハッピーパスの100%とサッドパス(異常系)のかなりの割合ですが、100%やそれに近い数字を達成しようとはしません。優れたオブザーバビリティ(可観測性)戦略と優れたエラーメッセージがある限り、後で新しいエラーモードをテストスイートに追加できるため、成功に向けた準備が整っています。
ベンチマーク#
クリティカルパスでは、パフォーマンスに関する推測ではなく実際の数字が必要です。トラフィックの多いコンポーネントに影響を与える変更については、遅いコードが本番環境に到達するのを防ぐために、明確なベンチマークが必要です。これはホットパスでのみ必要です。すべての些細なヘルパー関数にベンチマークを求めるのは、全員の時間の無駄です。
無駄のないロギング(Lean Logging)#
ログは実行可能(actionable)でなければなりません。不要なログはクラウドの請求額を増やし、個人情報を漏洩させる可能性があります。私はログレベルをチェックし、シークレットやPII(個人を特定できる情報)を含めることなく、必要なものだけを正確にキャプチャしていることを確認します。開発中の詳細な(verbose)ロギングは問題ありませんが、マージする前にクリーンアップする必要があります。
私が気にしないこと(ほとんどの場合)#
難しい問題に集中できるように、詳細の処理は自動化されたツールに任せています。機械にできることなら、人間がやるべきではありません。
フォーマット#
Goを使い始めてから、フォーマットのスタイルについて議論したことは一度もありませんが、特定の領域ではまだ存在していることは知っています。あなたができる最善のことは、標準を設定し、リンターとフォーマッターに処理させることです。標準が分かれば、コードエージェントもそれに従いやすくなります。CIパイプラインを通過すれば、それで問題ありません。これによりレビューがスピードアップし、無意味なスタイル論争が止まります。
些細な構文とコードの詳細#
問題を解決する方法はたくさんあり、特定の構文の選択を強制することは開発者の自由を制限します。ロジックがしっかりしている限り、for ループだろうとリスト内包表記だろうと私は気にしません。
個々のコードの各行#
LLMによって生成されたすべての行をレビューするのは、コンパイラや静的解析ツールの仕事です。代わりに、私はロジックと接続ポイントに焦点を当てています。
デバッグ#
私はデバッグセッションをほとんど行いません。何かが機能しない場合は、問題をシミュレートするための新しいテストを作成します。問題を再現した後でも何が起こっているのか把握できない場合は、オブザーバビリティとログが不十分であることを意味するため、それらの改善に焦点を当てます。私にとってデバッグは最後の手段であり、大量の「私はここ(I AM HERE)」というprint文(本来ならログ行であるべきもの)を追加することの同義語です。ミュータブル(変更可能)な状態に注意し、一時的な状態であっても保持するようにしてください。これにより生活がはるかに楽になり、デバッガーの必要性がなくなります。
エクスポートされていない名前(ただし例外あり)#
内部の名前はスコープが制限されており、全体的な設計に影響を与えることはほとんどありません。パブリックAPIがしっかりしていてコンテキストが明確である限り、私はそれらをざっと読み流します。これにより、レビューが進み、些細な選択について重箱の隅をつつくようなこと(nitpicking)を避けることができます。ただし、悪い名前はほぼ常に悪い設計につながり、常に保守が困難なコードにつながるため、私はまだ少しは気にしています。
マイナーな依存関係#
主要なフレームワークやクライアントライブラリ以外の依存関係のことです。これらがセキュリティのベースラインを満たしていれば、それほど気にする必要はありません。ただし、セキュリティの脆弱性や問題のあるライセンスがないか確認することは依然として必須です。もし1つの「ヘルパー」関数のためだけに何かをインポートしているなら、私は100%の確率でその関数を自分のコードで再実装し、依存関係を取り除きます。
結論#
これは万能のプロトコルではありませんが、最近の私がコードレビューにどのようにアプローチしているかを示すものです。また、コードベースをどのように計装(instrument)しているかについても、語るべきことはたくさんあります。コードレビューだけですべての潜在的な問題を捉えることはできません。だからこそ、私は自動化を強く推奨しており、エージェンティック・コーディングの時代においてはこれまで以上に重要です。現代のコーディングエージェントには、モデルを制約し、より決定論的な出力を得るための多くの拡張パターンがあります。[Agent Skills、フック、MCPツール、ポリシー、ルール… これらの多くは標準化されていないため、少し混乱するかもしれませんが、現在、その多くが標準化されつつある段階にきています。
車は、そのブレーキがサポートできる速さでしか走れません。お気に入りのコーディングエージェントのガードレールを学ぶことに投資し、あなたの貴重な時間を自動化できないもののレビューに使ってください。
Happy coding!
Dani =^.^=




