コードのクリーンさはコーディングエージェントに影響するのか? 制御された最小対研究
(arxiv.org)- 自律型 コーディングエージェント の評価は主に固定されたコードベース上でのタスク成功率を見てきたが、この研究はコード自体の クリーンさ が探索・修正コストを変えるのかを切り分けて測定した
- アーキテクチャ、依存関係、外部動作は同じで、SonarQube 規則違反と認知的複雑度だけが異なる 最小対リポジトリ 6組と、隠しテストベースのタスク 33件を構成した
- Claude Code と Claude Sonnet 4.6 を用い、各タスクをリポジトリ対の両側で10回ずつ実行して、合計 660回の実験 を行った。エージェントはどちらのコードであるかを知らされていない
- コードのクリーンさは 通過率 を変えなかったが、よりクリーンなコードではトークン等価指標が 7〜8% 減少し、ファイル再訪は 34% 減少した
- モデルやプロンプトだけでなく、コードベースの状態 もエージェントの計算コストと探索効率を左右する実務上の変数として残っている
研究課題と問題設定
- 自律型コーディングエージェントは急速に普及している
- 2026年に 128,018 件の GitHub プロジェクトを調査したところ、最初の実用エージェントの公開から1年も経たない時点で 22〜29% のプロジェクト にエージェント活動の痕跡が見つかった
- エージェント実行コストも小さくない
- SWE-bench Verified では単一タスクがフロンティア LLM 基準で平均約 400万トークン を使用する
- 全体使用量では入力トークンが大半を占める
- 既存の評価は SWE-bench のようなベンチマークで タスクを解決できたか に集中してきたが、最近の研究では通過率とあわせて資源使用量も測定し始めている
- 一般的な比較はコードベースを固定したうえでエージェントやスキャフォールディングを変える方式だった
- この研究は逆にエージェントとタスクを固定し、コードベースのクリーンさ だけを変えて比較する
コードのクリーンさと最小対の構成
- コードのクリーンさは、保守しやすいコードと結びつく特性の束として扱われる
- 読みやすさ
- 低い認知的複雑度
- 適切に分離されたヘルパー
- 明確な命名
- 少ないデッドコード、重複ロジック、偶発的結合
- 研究ではコードのクリーンさを厳密な形式定義に固定せず、SonarQube の静的解析ルール違反数を緩やかな代理指標として用いる
- 使用ツールは SonarQube Cloud Enterprise Edition
- ルールセットは “default quality gate”
- 最小対リポジトリは内部のクリーンさだけが異なり、次の条件は揃うように構成される
- 同じ言語とフレームワーク
- 同じ依存関係
- 同じテスト、または同等のテストカバレッジ
- 同じ外部動作
- 動作の同等性は、同じ入力に対して外部から観測可能な出力と状態遷移が同じ場合とみなす
- 実際の検証は、同じテストスイートを同じカバレッジで通すか、リファクタリングのような観測不能な変更を補正したテスト対を通す形で行われる
最小対生成パイプライン: Slopify と Vibeclean
- 最小対は双方向に作られる
- クリーンなリポジトリをより雑然としたものにする Slopify
- 雑然としたリポジトリを整理する Vibeclean
-
Slopify
- クリーンなコードベースを、コードレビューや linting なしに成長したかのようなバージョンへ変える
- 意図的に壊したコードではなく、静的解析がなかった場合の代替歴史版を目指す
- 3段階をそれぞれ新しいエージェントが実行する
- Build: リポジトリをビルドしてテストが通るようにしたうえで、
build instructions.mdにコマンドを固定する - Explore: リポジトリを見て回り、整理対象ディレクトリごとに
summary.mdを作成する - Transform: 指定ディレクトリに SonarQube ルール違反を導入し、各パス後にテストを再実行して壊れた変更は却下する
- Build: リポジトリをビルドしてテストが通るようにしたうえで、
- ヘルパーのインライン化、パスごとのロジック重複、デッドコードの追加、一部モジュールの単一ファイルへの統合などで認知的複雑度を高める
-
Vibeclean
- 自然にルール違反の多いコードベースを、外部動作を保ったまま整理する
- エージェントの作業リストはアナライザが検出した issue 一覧で、各 issue はコード範囲に結び付けられる
- 修正範囲はアナライザが示した問題に限定され、全面的な再設計は目的ではない
- 2段階で動作する
- Build: ビルドとテストのコマンドを確認し、
build instructions.mdに固定する - Clean: モジュールごとにアナライザのルール違反を機械的に整理し、モジュール処理後にテストで動作同等性を確認する
- Build: ビルドとテストのコマンドを確認し、
- 文字列リテラルの重複除去、コメントアウトされたコードの削除、レガシーなコレクション慣用句の置き換え、デッドブランチの除去などを行う
- アナライザが実際に巨大構造を指摘した場合には、200行超の dispatch switch を名前付きヘルパーに置き換えたり、2,800行のクラスから永続化ヘルパーを抽出したりする
- ただし抽出が複雑度を除去するというより、より多くのメソッドへ再配分してしまうこともあり、一部の最も大きな巨大構造は
wontfixのまま残る
ベンチマークリポジトリとタスク設計
- ベンチマークは Harbor framework v0.4.0 上に構築されている
- 合計 6組の最小対リポジトリが使われた
- Java 中心 3件、Python 中心 3件
- 一部のリポジトリは他言語のコードを少量含む
- 公開オープンソースリポジトリ 3件と、非公開 SonarSource コードベース 3件で構成される
- 非公開の対は、評価対象 LLM が公開リポジトリから学習している可能性に備える 暗記防止 の役割を持つ
- リポジトリごとのクリーン側と雑然側の主な数値は次の通り
sonar-sca*: issue 94 / 2,825、issue 密度 0.73 / 20.66、認知的複雑度密度 30.6 / 56.5sonar-caas-poc*: issue 16 / 855、issue 密度 0.61 / 27.16、認知的複雑度密度 179.8 / 218.9sonarcloud-codedatalake*: issue 199 / 1,319、issue 密度 4.36 / 34.39、認知的複雑度密度 34.0 / 216.5commons-bcel: issue 694 / 2,711、issue 密度 12.60 / 49.46、認知的複雑度密度 102.8 / 108.3genie: issue 152 / 1,262、issue 密度 1.28 / 10.81、認知的複雑度密度 22.2 / 23.5ckan: issue 1,006 / 3,632、issue 密度 7.54 / 27.50、認知的複雑度密度 69.3 / 76.5
- タスク設計は3つのルールに従う
- ホットスポット経由: 対の両側で issue 密度と認知的複雑度の差が大きいコード領域を通るようにタスクを配置する
- 外部観測可能な説明: 入出力と例示シナリオのみを提供し、ファイル名・関数名・内部構造名は与えない
- 公開表面テスト: CLI、HTTP ルート、ライブラリ/API など、アプリケーションが呼び出し元に提供するインターフェースを通じて隠しテストを実行する
- タスク生成はエージェントと人間で分担した
- エージェントがクリーン変種と雑然変種を比較して差分マップを作る
- 別のエージェントがタスク概要とテスト可能性を書く
- 人間がその中からもっともらしく興味深い概要を選択・編集・キュレーションする
- 3番目のエージェントが実際の指示文、隠し公開表面テスト、内部用参照実装を作る
- 参照実装は両方のリポジトリで隠しテストに通らなければならない
- 修正前のリポジトリは隠しテストに通ってはいけない
- 2回の反復後も条件を満たせないタスクは、人間が書き直すか削除する
- 最終タスク数は 33件 で、3つのトラックに分かれる
- 13件の認知ホットスポットタスク: 単一メソッドまたは単一クラスの高密度複雑領域を通る
- 14件のマルチモジュールタスク: 2つ以上のモジュールをまたぐ変更が必要
- 6件の補正タスク: 両側が同一領域で単純なタスクを実行し、クリーンさと無関係な変化があるかを確認する
実験設定と測定指標
- すべての実験はデフォルトのツールセットを使う Claude Code で実行された
- 報告された数値は Claude Sonnet 4.6 の実行から得られたもの
- Claude Haiku 4.5 でも同じタスクセットを一通り試したが、通過率が低すぎて footprint の差を明確に読み取りにくかったため、本結果からは除外された
- エージェントはタスク説明だけを読む
- コードのクリーンさに関する追加のプライミングは受けない
- 自分が最小対のどちら側で作業しているかを知らない
- 各タスクは対の両側で10回ずつ実行される
- 総実験数は 33 × 2 × 10 = 660回
- 各実行はコンテナ化されたサンドボックスで行われる
- CPU、メモリ、ストレージ、実時間に制限がある
- 公開パッケージレジストリにはアクセス可能
- ベースイメージにはリポジトリごとのツールチェーン、ビルドキャッシュ、サービスが含まれる
- 対の内部では
/appにマウントされるソースツリーだけが異なる
- 記録指標は10項目
- 通過率: 最終状態で隠しテストが通った比率
- 入力トークン: 全ターンでモデルが読んだトークン数で、ファイル内容と過去対話の再送が大半を占める
- 出力トークン: モデルと下位エージェントが生成した成果物全体で、散文・コード・推論の痕跡・ツール呼び出しを含む
- 推論文字数: Anthropic API が推論トークンを別途公開していないため、推論コンテンツブロックのプレーンテキスト文字数を数える
- 対話ターン数: エージェントとツールのやり取り総数
- 初回編集前ターン数: 最初のファイル修正までにかかったターン数
- 初回編集前文字数: 同じ区間の対話文字数
- 読んだファイル数: 実行中に開いたユニークなファイル数
- ファイル再訪: すでに読んで編集したファイルを再び読んだ回数
- 修正行数: 最終パッチが変更したソース行数
- ファイル再訪は
読む → 編集する → 別の作業が入る → 再び読むのような流れで現れる- 研究ではこれを、広い探索よりも以前の編集に対する 不確実性 のシグナルとして解釈する
- エージェント footprint 指標は、同じタスクを固定温度で反復しても大きく変動しうるため、外れ値フィルタを適用する
- 各
(タスク, 側)組み合わせの中で、10回反復の中央値から 50% 以上外れた実行を平均前に除外する - 実際には全実行の 9.7% が除外された
- 各
- データセット水準の数値は 33件のタスクに対してマイクロ平均される
- 各指標についてタスク別のクリーン側平均と雑然側平均を合算し、その比率差を計算する
- 通過率だけは例外的に、クリーン側と雑然側の絶対パーセンテージポイント差で報告される
結果: 成功率より探索コストに影響
- コードのクリーンさはエージェントの 通過率 には有意な変化をもたらさなかった
- よりクリーンなコードではトークン等価指標が 7〜8% 減少 した
- ファイル再訪は 34% 減少 し、クリーンなコードが同じタスクでも読み返しコストを減らせることを示した
- したがって、コードのクリーンさはモデル選択、ハーネス、プロンプトと並んで、エージェント実行コストを左右する別軸とみなせる
1件のコメント
Hacker News のコメント
研究する価値のある興味深い問いではあるが、実験設計にはかなり懐疑的
実験では Opus 4.6 を使って「品質を落とした」または「整理された」コードベースを合成し、相対比較に使っている
さらに悪いのは、アプリケーションのテストを壊したかどうかを統制していない点
「合格率は、各タスクについて私たちが書いた隠しテストの基準でエージェントの最終状態を採点する。リポジトリに既にあった無関係なテストをエージェントが壊したかどうかは確認せず、クリーンな側と雑然とした側の解法がどちらも隠しテストに合格しても、採点対象外のテストでは差が出る可能性がある」
最終成果物の品質を統制しないなら、トークン消費量に関する結論はほとんど意味がないように見える
公平に比較するには、各ペアの両方のプロジェクトで動作する単一のテスト群を書く必要があるだろう
研究が良いという意味ではないが、テスト合格がエージェントの有効性と必ずしも相関するわけではないので、その判断は理解できる
機能的には問題なさそうに見えるが、作業を終えるのにより多くのトークンコストがかかる状況を示す程度
AI 疲れはとっくに退屈なものになっていて、今ではただ苦痛なほどだ
私の経験では、コードベースにデッドコード、重複コード、到達不能な代替経路、漏れのある抽象化、未成熟な設計パターンが散らばっている場合と、データフローが明確でカプセル化と構造がきれいな場合では、エージェントの性能差はかなり大きい
悪いコードでは、どの最前線モデルも何度もコードレビュー、品質確認、修正ラウンドを経る必要があった一方、良いコードでは 1〜2 回目の試行でそのまま正解する例を見た
LLM にこうした項目を確認するスクリプトを実行させ、同じスクリプトを pre-commit フックとして強制することもできる
私が扱うすべてのコードベースにこうした設定を徹底的に入れたことが、エージェント型コーディングで最も大きな効果をもたらした
私が使っている複数のリンターについて、より詳しくまとめた記事はこちら: https://www.balajeerc.info/Use-Deterministic-Guardrails-for-...
エージェントも、巨大でぐちゃぐちゃなコードベースより、きれいなコードベースの方がうまくやれるに決まっていると思う
よくできた仕様とドキュメントへのアクセス権があるとよりうまくやれるのと同じだ
ある時点から、ひどいパターンが自分にも染みつき始める
ここには問いに答えようとする論文があり、逸話的な証言は読者にバイアスを与えるだけで、問題について客観的に結論を出す価値を加えない
全員が論文を読み、方法論や結果を批判する方向が最も有用な議論だろう
ただ、LLM は大して良くないと不満を言う人たちは、概して雑然としたコードベースを持っているタイプに見える
私がうまくいくと見ているコツは、Python なら次のようにリファクタリングを指示すること
「Python コードをより Python らしくリファクタリングせよ。たとえばクラスやシングルトンを減らし、特に速度向上があるならそうせよ。Python コードは、ベンチマーク性能の回帰なしに、人気のあるオープンソース Python パッケージのコードで期待されるコード構成基準に必ず従うこと」
Rust コードにはこうした変形を使ってみた
「
/srcの Rust コードベースが、複数の 1,000 行超のファイルに肥大化している。ベンチマーク性能の回帰なしに、人気のあるオープンソース Rust コードで期待されるコード構成基準に合わせて Rust コードベースをリファクタリングせよ」こうしたプロンプトは、a) コードを論理的に再構成し、b) ファイル名が関連コードの位置について意味的なヒントを与えるため、エージェントの性能も良くなるようだ
5,000 行の肥大化したファイルでは、エージェントが関連コードを探すために複数の塊を読む必要があり、非効率だ
ベンチマーク性能も通常リファクタリング後に良くなるが、特にコンパイルされる Rust では偶然のような気もするものの、不満を言うことではない
基本的に、エージェント型コーディングツールはコードを削除することに消極的な傾向がある。削除しろと言ってもそうで、古いコードを残したり、そのコードが引き続き呼び出され得るように複雑さを追加しようと、あらゆる努力をする
単にプロトタイピング中なら本当に厄介で、結局デッドコードが大量に積み上がり、後で機能を追加しようとすると混乱を招く
これが分かっていれば、単にレガシーをなくせと頼めばよい
コードベースをきれいに保つと、AI が正しいことをするよう促される。テストが多ければ新機能を作るときにさらに追加し、ドキュメントがあれば別途言わなくても更新する
コードハーネスが改善されるにつれて、こうしたものは次第に組み込まれていくだろうし、プロンプト経験の少ない人でもまともな結果を得やすくなるだろう
通常はまずレビューさせてレビュー項目のリストを作らせ、その後、各項目を一緒に見ながら私がイエス/ノーで決めるか、追加修正を提案する
特にシステム全体に対する慎重なエンドツーエンドテストがないならなおさらだ
どのみちコードスタイルのルールはすでに CLAUDE.md に入れてある
「散らかったリポジトリを整理するエージェント・パイプライン」というアプローチはひどく見えるし、それだけで研究全体を否定するのに十分だと思う
この作業では最小ペアの半分がそのような方法で作られたように見える
AIが「整理した」リポジトリが実際に良いコードベースを代表していると仮定しなければならない結論は、まったく信用しない
ここでいう「クリーンさ」は、エージェントに単にもっと良いコードを書けと頼んだものではありません
50〜100個の静的解析ルール違反のリストとコード行数を渡し、それを取り除くよう依頼しました
その後、ルール違反が解消されたかを確認しました
LLMでコードを書き直してこうした違反を取り除くことは、かなり受け入れられている慣行です
Sonarの既存のワンショットLLMベースのアプローチ [1] は1年以上運用されており、最近のエージェント型アプローチ [2] も同じ作業をかなりうまくこなします
[1] https://www.sonarsource.com/solutions/ai/ai-codefix/
[2] https://www.sonarsource.com/products/sonarqube/remediation-a...
当然、影響があるに決まっていると思う
どんなモデルも実際のコードベース全体をコンテキストに入れることはできず、人間のようにコードを見て回る必要がある
検索してファイルを読むというやり方だ
ファイルが予想される場所にあり、モデルや人間が最初に検索しそうな名前で呼ばれていれば最初の試行で見つかるが、そうでなければ深い検索と複数回の試行が必要になる
すべてのパスを見て、すでに扱った領域は無視しながら次のパスへ進めばいい
開発者のやり方とかなり似ている
NJITで進行中の似た取り組みでも同様の結果を見た。私たちはこれを文脈的品質伝染と呼んでいる
ここで興味深いのは、業界でよくある現実の状況だ。品質が混在したコードベース、レガシーなコードパターンとより新しい「良い」パターンが混ざり、エージェントが慣習を混同するようなコードベースなどだ
最小ペア設計は、実は強みのひとつだ。リポジトリ同士を比較するよりも、構造、依存関係、テストといった他の要因からクリーンさを切り分けようとしているからだ
ただし、LLMが生成した「ぐちゃぐちゃ化された」コードを使うことには、機械的または人間がガイドした方法ではないという点で、やや疑問がある
最大の批判点は、他の人たちが正しく指摘している通り、テスト一式全体を確認しなかったという選択だ。「振る舞いの同等性」という主張は、テストとカバレッジの範囲でしか有効ではない
この仮説に説得力がある理由は2つある。1) LLMはコードベースで見たものを模倣するので、ゴミを入れればゴミが出るという意味で筋が通る 2) 過去1〜2年、多くのエンジニアがこれらのモデルを使いながら直感的に感じてきたことと一致している
グリーンフィールドは、忙しいコードベースに加わるよりほぼ常に簡単であり、混乱は複雑な統合やレガシー目的のシステム保守などから生じる
エージェントが自分の残したスタブやWETコードをかき分けて進む方法を学ぶとしても、人間が実際に何が起きているのか追えないコードベースを本当に望むのか?
だから個人的には、少なくともコードが何をしているのかをコードで語ってほしい
小さな関数2つに共有ヘルパーへ切り出せるロジックがあっても、人間のプログラマなら、その抽象化が汚くなり、どちらか一方を少し変えようとするだけで壊れると分かるので、そうしないことがある
これを数値化して見るのは興味深い
クリーンな構造は人間とエージェントの双方の認知負荷を下げるようで、だから命名とモジュール化が思った以上に重要だという説明になる
定量化は難しいが、すべてのコード品質指標が最終的に捉えようとしているのもそれだ
その基準で見れば、使っているコード品質指標が合理的でありさえすれば、かなり驚きの少ない結論だ
品質指標がコーディングエージェントの文脈で良いものなら、期待される結果はまさにこれだ
多くのトークンはコード探索に使われる。コードを探したり呼び出し箇所をたどったりして、作業を行うのに十分なコンテキストを作る過程だ
エージェントに何らかの形のLSPアクセス権を与え、モノレポならAGENTS.mdのようなファイルで階層的な案内を提供すれば、探索にかかるトークン使用量を大きく減らせる
しかし散在したコードベースでは、各タスクを解くために結局何らかの形で探索が必要になる
そしてこの探索は単なるトークン使用だけではない。各ステップごとに、LLMの待ち時間、プリフィル、デコード、出力からエージェントのパース、ツール呼び出し、ツール応答を経て再びLLMに戻る往復遅延が繰り返される
一部は並列化できるが、実際には大半が逐次的なので、作業はかなり遅くなる
エージェントを効率的に使うには、局所性と構造が鍵だ。コンテキストウィンドウは常に限られており、その中での注意も一貫しているわけではない
私の経験では、エンジニアに影響するものはすべてエージェントにも影響する
良い抽象化、適度な大きさのメソッド、良い名前、原則に沿ったサービス内・サービス間の構造、ユニットテストなどがすべて該当する
これらは歴史的にエンジニアの仕事であり、他の人がコードに貢献しやすくするためのものだった
今では他の人だけでなく、他のエージェントもコードに貢献しやすくしてくれる