- エージェントが作成した17,155行規模の Rust データアクセス層を段階的にリファクタリングしたところ、同じ機能変更に必要な入力トークンが 159,564 個から 27,360 個へと83%減少した
- コード全体の量はほぼ変わらなかったが、関連コードを凝集度の高いファイルへ分離したことで、エージェントが変更に必要な最小限のファイル集合だけを読めるようになった
- 入力トークンは最大ファイルが十分に小さくなるまで大きく減らず、最終的にデータ層を 19 個の Rust ファイルに分割し、最大ファイルサイズを 17,155 行から 3,695 行へ削減した
- 出力トークンと機能実装量はほとんど変わらず、Claude も適切なリファクタリングを自ら選択したり安定して実行したりできなかったため、計画と実行には人間による積極的な指示が必要だった
- Sonnet 5 の入力価格 $3/MTok を基準にすると、変更 1 回あたりの節約額は約 $0.397 にすぎないが、その後データアクセス層を修正するたびにコストが繰り返し下がる可能性を確認した
エージェントが作った 17,155 行のファイル
- 業務支援用アプリケーションは、動的な更新・検索が可能な Web UI、モーダルと自動保存、外部システム連携、機械学習とテキスト分析、バックグラウンドジョブ、自動デプロイ環境を備えている
- 全体約 15 万行のうち Rust が約 12 万行で、残りは TypeScript と Terraform であり、大半は Claude Code と一部 Cursor を使ってエージェントが作成した
- 開発者は興味本位でたまに眺めた場合を除き、コードを読んだりレビューしたりしていなかった
- データアクセス層は、すべての読み書きクエリで同じ HTTP リクエスト設定と JSON エンコード・デコードを繰り返しながら 6,000 行以上に成長し、最終的に 1 つの Rust ファイルが 17,155 行に達した
- このモジュールには重複排除や内部言語がなく、関数抽出は限定的でクラス抽出もほとんどなかったが、保持すべきインターフェースと明確な境界があり、リファクタリング実験に適していた
同じ変更を繰り返した測定方法
- 目的は、現在のリファクタリングにトークンを投資することで、将来の機能変更におけるトークン消費を下げられるか確認することだった
- エージェントは以前の作業から学習しないため、各段階で新しいサブエージェントにまったく同じ変更を依頼し、学習効果が混ざらないようにした
- 実験は次の順序で行った
- 厳格なリファクタリング原則に従って全体計画を作成する
- 1 つのプロンプトで代表的な変更を定義する
- サブエージェントに変更を実施させ、トークン消費量を報告させて基準値を測定する
- 変更結果を破棄したうえで、リファクタリングを 1 段階適用する
- 同じ変更を再度実施し、結果を破棄する過程を繰り返す
- 段階ごとのトークンコスト、実行時間、コード行数を記録する
- Claude がリアルタイムのトークン数を信頼できる形で提供できなかったため、送受信文字数を報告させ、tiktoken で文字数を 4 で割ってトークンを近似した
段階別の測定結果
- 基準状態では、データアクセス層と最大ファイルはいずれも 17,155 行、Rust コード全体は 50,359 行で、代表的な変更には入力 159,564 トークン、出力 1,705 トークン、342 秒が必要だった
- 15 段階後、データアクセス層は 16,608 行、最大ファイルは 3,695 行、Rust コード全体は 49,812 行で、入力は 27,360 トークン、出力は 2,113 トークン、実行時間は 454 秒だった
- 中間段階では、最大ファイルが小さくなるにつれて入力トークンも減少した
- 7 段階目で
queries.rsを抽出した後、最大ファイルは 15,670 行、入力は 151,850 トークンに減った - 8 段階目で
traits.rsを抽出した後、最大ファイルは 13,845 行、入力は 132,558 トークンになった - 12 段階目で
store/を分離した後、最大ファイルは 9,269 行、入力は 104,080 トークンまで減少した - 最後の
store/分離後、最大ファイルは 3,695 行、入力は 27,360 トークンへ急減した
- 7 段階目で
- 最終的なデータアクセス層は 19 個の Rust ファイルで構成され、最も大きいファイルはテストライブラリになった
- 追加のリファクタリングでも、同じ方法をそのテストファイルに適用できる
入力トークンが 83% 減った理由
- 同じ作業の入力トークンは 159,564 個から 27,360 個へ減り、132,204 トークンを節約した
- データアクセス層のコード全体の量はほとんど変わっていないため、読むべきコードそのものが減った結果ではない
- エージェントが作業に必要な最小限のファイル集合を特定し、徐々により小さなコード領域だけを読むようになったことは、Claude Code の思考出力やファイル読み取り要約からも確認できた
- ファイルを任意に細かく分けるだけでは、関連コードを探すために複数ファイルを読む必要があるため、同じ効果は得にくい
- 最大の減少は最後の分離で発生したが、それ以前の段階で重複を抽出し、繰り返し現れる中核構造を作ったことで、ファイル分離が可能になった
- この順序はコスト削減を目標に事前設計したものではなく、ローカルな重複を先に取り除き、共通の中核が明らかになった後で小さなファイルへ分解する、一般的なリファクタリング過程から生じた
出力トークンと金銭的効果
- 代表的な変更を作成する際に生成された出力トークンはほとんど変わらず、リファクタリングは実際の変更サイズまでは削減できなかった
- 出力トークンの価格は入力トークンの 5 倍だったが、絶対量ははるかに少なかった
- Sonnet 5 の入力価格を $3/MTok として計算すると、変更 1 回あたりの節約額は約 39.7 セントである
- デバッグやより複雑な機能、コードベース全体のリファクタリングでも節約が蓄積するか、またリファクタリング自体のコストがいくらかは確認されていない
- 出力トークンまで減らすリファクタリングが可能かも不明であり、単純な代表的変更では、非決定的なコード生成のノイズが構造変化による差を覆い隠した
Claude と進めたリファクタリング
- Claude はコードを見て適用すべきリファクタリングを自ら選択できず、実際の結果はプロンプトが直接指示した作業に対応していた
- 開発ハーネスには明示的なリファクタリング段階があったが、Claude はそれを通じて 17,155 行のファイルを改善しなかった
- 計画策定の過程で、Claude Code は関数抽出を最初の段階として見つけた一方、Claude.ai はクライアントクラス全体の抽出まで特定した
- 機械的な変更には
grepとsedを使う Python スクリプトを活用したが、スクリプトはインデントのためにたびたび混乱した - 最も価値が大きかったストアファイルの分離は初回の試行で漏れており、後続段階として改めて適用したため、測定結果の段階数と付録の計画段階は一致しない
- 実験全体には約 8 時間かかり、大部分は無人で進行した
- 6 時間 40 分後に漏れていた段階を発見し、1 回介入した
- 遅いホテルの Wi-Fi よりも、肥大化しすぎた Cargo の一時ビルドキャッシュがテスト実行を大きく遅らせた原因だった
代表的な変更プロンプト
- 各サブエージェントにはコードベースとアーキテクチャ文書だけを与え、同じ
ItemWatchStore非同期公開トレイトを実装させた - トレイトは次の 3 つのメソッドを含む
watch_item(&self, item_id: &str, user_id: &str) -> Result<()>unwatch_item(&self, item_id: &str, user_id: &str) -> Result<()>watched_items_for_user(&self, user_id: &str) -> Result<Vec<String>>
- watch 情報は Firestore の
item_watchesコレクションにitemId、userId、createdAtフィールドとして保存する - 別個の Rust レコード構造体は使わず、item ID の
Vec<String>を返す FakeStoreにはメモリ内のVec<(String, String)>フィールドを追加し、FirestoreStoreには既存の HTTP パターンをそのまま使って実装する- 応答の末尾には、読んだファイルと文字数、応答文字数を JSON で出力させ、コードはコミットしないよう指示した
適用したリファクタリング計画
-
1 段階目 —
FirestoreClientクラス抽出- ドメインクエリの調整と Firestore HTTP 送信の責務を分離する
reqwest::Client、project_id、MetadataAuth、URL・認証ヘッダー処理を新しい構造体へ移す- 計画上は
FirestoreStore実装から約 1,200 行を減らし、クライアントに約 120 行を追加する
-
2 段階目 —
extract_doc_id、new_link関数抽出- 20 個のドキュメントパーサーにおける ID 抽出と、62 回繰り返された
Link生成を共通化する - 約 500 行の削減を見込む
- 20 個のドキュメントパーサーにおける ID 抽出と、62 回繰り返された
-
3 段階目 — リンククエリパイプライン関数抽出
- 約 15 か所のクエリ結果収集と、約 8 か所の単一対象 ID 参照パターンを共通化する
- 約 200 行の削減を見込む
-
4 段階目 —
FakeStoreInnerリンク条件関数抽出- 約 15 個のメソッドで繰り返されていた
inner.links.iter()の変形を 2 つのメソッドに分離する - 約 120 行の削減を見込む
- 約 15 個のメソッドで繰り返されていた
-
5 段階目 — Firestore 値生成関数の導入
- 128 回以上繰り返された文字列・タイムスタンプなどの
json!表現を 4 つの関数呼び出しに置き換える - 複数行マクロを 1 行の呼び出しに変え、約 80 行の削減を見込む
- 128 回以上繰り返された文字列・タイムスタンプなどの
-
6 段階目 —
FieldsBuilder抽出- 約 20 個のエンコーダーにおけるフィールドマップ生成パターンをビルダーに統合する
- 約 40 行のエンコーダーを約 12 行に減らし、合計 500〜600 行の削減を見込む
-
7 段階目 —
queries.rs分離- 32 個の
LinkQuery定数と関連型を別モジュールへ移動する - 既存の呼び出し側を変更せず、
mod.rsから約 800 行を減らす
- 32 個の
-
8 段階目 —
traits.rs分離- 17 個の公開トレイトと関連エラー型を移動し、再エクスポートする
mod.rsから約 1,900 行を減らすが、新しいファイルも約 1,900 行になる
-
9 段階目 —
traits/のドメイン別分離- トレイトを
planning.rs、content.rs、people.rs、system.rsに分ける - 定義と呼び出し側を変えず、ファイルごとのサイズを約 300〜650 行に抑える
- トレイトを
-
10 段階目 —
codec.rs分離- ドキュメントエンコーダー・デコーダー、パーサー、
FieldsBuilder、値生成関数を移動する - 6 段階目以降は約 400〜500 行のモジュールとなり、
mod.rsから約 500 行を減らす
- ドキュメントエンコーダー・デコーダー、パーサー、
-
11 段階目 —
fake_store.rs分離FakeStore、FakeStoreInner、18 個のトレイト実装を移動するmod.rsから約 4,700 行を減らす
-
12 段階目 —
FirestoreStore実装の分離store/mod.rsには構造体、コンストラクタ、FirestoreClient、MetadataAuthを置き、トレイト実装はドメイン別ファイルに分ける- 約 10,000 行のファイルを、それぞれ 120〜650 行の 10 個のファイルに変え、
mod.rsは約 100 行の再エクスポートファイルにする
-
13 段階目 — テストを対象モジュールと同じ場所に配置
- テストコードは変更せず、各実装ファイルの下へ移動する
mod.rsから約 2,000 行を減らし、各ファイルには関連テスト 200〜700 行が追加される
限界と今後の実験
- リファクタリング計画の作成と実行にかかったトークンを別途数えていないため、リファクタリング投資コストを正確に計算できない
- 該当時間帯の全使用量を基準にした上限は 500 万トークンだが、ここには計画を 2 回作成した作業、実験および代表的変更の設計、その他の作業がすべて含まれる
- 実験対象はまだグリーンフィールド段階であり、1 人の開発者が構築・管理する 1 つの大規模アプリケーションであるため、結果を一般化することはできない
- 今後の作業として、正確なリファクタリングトークンの測定、より複雑な変更、より広範囲のリファクタリング、継続的なリファクタリング、アプローチ別の相対的価値比較が必要である
- 今回の実験は、リファクタリングで得られる時間的・金銭的価値とリファクタリング自体のコストを併せて測定するための出発点となる
1件のコメント
Hacker News の意見
ほとんどのIT企業が無視してきた開発者のベストプラクティスが、AIのベストプラクティスとして再発明されているのが面白い
以前は、コードの中にドキュメントを置き、Jiraのタスクだけを投げるのではなくプロジェクト全体の文脈を共有し、長期的な生産性のためにリファクタリングしようと言っても退屈がられていた
今では同じ内容でも、AI向けドキュメントをコードやCLAUDE.mdに置き、プロンプトで細かく制御しすぎず、AIの生産性のためにリファクタリングしようと言うと興味深く受け止められる
人間は正しいやり方を分かっていても忙しかったり集中力が切れたりするが、エージェントは退屈な作業にうんざりしないので、人間に有効だと証明されながら継続適用が難しかった手順が現実的に可能になる
共通仕様に基づいて実装とテストを別々のエージェントに書かせ、監査エージェントに検証させて互いの結果に汚染されないようにしたが、これはIBMが1980年代に人間向けに開発したクリーンルーム工学をAIで広範かつ一貫して適用したものだ
古い慣行をAIブームに合わせて新しいもののように包装しているのではなく、20年以上前のベストプラクティスが今でも有効だという点を根拠つきで示している
エージェントはセッションごとに文脈を学び直さなければならないため、ベストプラクティスの価値がはるかに高まり、効果もすぐに現れる
それでも、1時間のあいだにCLIを100回実行して新しいフラグの使い勝手を試せるのは良い
一方AIは、こうした基盤がなければ非常にひどい働きしかできないか、まったく動かないので、健全なエンジニアリング慣行は長期的な改善策ではなく必須の前提条件になる
実際の純効果がまったくなくても、AIをワークフローに入れることで、きちんとした開発慣行を導入する口実ができるという点では有用だ
この記事は、AIツールが実際にどう使われているかに基づいて具体的かつ定量的に批判している点が良い
実際の利用事例もなく社会的リスクを漠然と論じる記事より、AIに何ができないのかを測定値で示す記事のほうがはるかに有益だ
同じ理由で、Boko Haramの構成員にインタビューしてAIがテロにどう活用されたかを調査した報告書も印象的だった
AIを使わず自分でやるリファクタリングが本当に好き
見た目の変化はないが、今すぐ成果が見えないWebサイトを将来ずっと扱いやすくすることに満足感がある
既に確立されたパターンで解決済みの問題を、昔の奇妙な回り道コードがもう一度解こうとしている箇所を見つけて、ベストプラクティス側へ移しつつ新たな技術的負債を作らない過程がパズルのようで楽しい
認証に至るまですべてを雑に自前実装して難しいやり方で内部原理を学び、その結果として今後10年楽しめるリファクタリングの種も手に入った
コードベースはひとつのシステムという言葉を具体的に理解できるようになり、コードを引っ張ったり押したりできる連続的な構造や網のようなものとして、高いレベルから見られるようになった
AIがこうした学習過程をなくしてしまえる点が、ジュニア開発者の問題を浮き彫りにしている。直感を得るには自分で深く掘り下げるしかなく、Naurが40年前に警告していても、この教訓は繰り返し忘れられる
エージェントがリファクタリングする際には、人間の関与が不可欠だと考える
生成モデルが初期作業に集中して見落とした部分をレビュー用モデルが見つけられることはあるが、プロジェクト全体の目的とコードがどう結びつくかを実際に理解し、重複やより洗練された構造を見分けられるかは疑問である
コーディングエージェントにリファクタリングを任せるのは、外傷外科医に運動能力を高めてほしいと頼むようなもので、きちんとやるには全体論的な視点が必要である
大きなファイルを複数のファイルに分けるだけでは表面的なリファクタリングにとどまる。どのコードを一緒に置くべきか、何をユーティリティ関数として抽出すべきかという理論がなければ、因数分解ではなく大きな数を小さな数に分けてからまた足し直すのに近い
エージェントは、APIですでに取得している値を再び保存して計算するシステムを作ることもあるが、人間はプロジェクト全体を見渡して、JSONの特定のキーに必要なデータがすでにあることを正確に見つけ出せる
functools.partialで Commandパターン を実装しろという複雑な要求にも対応可能であるファイル境界は論理的なサブシステムの境界を表して推論を容易にし、他ファイルの内容は基本的に不透明なものとして扱う、という概念も学習データに豊富に含まれている
この構造の利点は、人間の認知の偶然的な特性だけではなく客観的な側面もあると思う
リファクタリング経験や他人のコードベースを扱ってきたやり方、元の設計者として過去と現在の意図を説明できた点が役立った
依存関係の脆弱性が少なく、LLMが扱いやすい非主流言語であり、JavaScriptとPython寄りのバイアスを取り除いた後は作業速度が大きく伸びた
複数の人気言語でスクリプトを書きつつ、環境要件に応じてすべてJVM上で実行するJSR-223プロジェクトだった: https://en.wikipedia.org/wiki/Scripting_for_the_Java_Platform
この採用市場では、最新のフロンティアモデルベースのコーディングエージェントを使い、その能力と限界を正確に把握している必要があり、面接で逆の評価を出すと不採用の理由になり得る
簡潔なコンテキストはトークン消費を減らすだけでなく推論を改善し、1つのコンテキストにより多くの階層を入れて知的に扱えるようにする
良い抽象化を目指すリファクタリングは、試したケースだけでなく内挿・外挿したケースでも当たりやすい、より一般化性能の高いソフトウェアを生み出す
これを裏づける情報理論やベイズ数学があり、経済性・エネルギー効率の高いソフトウェアがより正確になるのは絶妙な偶然のように見える
要点はコードのエントロピーを下げることである
LLMはすでに少ないコンテキストでも意味をかなりうまく推論する
データが提示されている点が興味深く、よく分離されたコードではLLMが大きく得をする一方、自分でそういうコードを作る能力はそれほど高くないという自分の実感と一致する
たぶん大半の人間の開発者も同じだろう
全体としてAIの利得は大きいが、整理の時間も必要で、依然としてすべての行を読むべきだと思う
他チームの進行を止めないよう一部レビューを後回しにした結果、あとで普段より大きな技術的負債を返済しているが、先にボトルネックを解消するほうに価値があった
AIはあらゆる意味で技術的負債をより簡単に借りられるようにしたし、誘導さえうまければ負債解消もかなりうまくやる。ただし結果は人による: https://news.ycombinator.com/item?id=49035455
よく構成されたコードの例やオープンソースのリポジトリを通じて、何をすべきで何を避けるべきかを示すと大いに役立つ
リファクタリングの経済的利益の大半は、トークン節約よりも人間の理解度向上から生まれる
深夜3時の障害対応をより早く解決でき、本番環境に入るバグを減らし、競合より速くリリースできる
何より、システムを理解すれば責任とオーナーシップを進んで引き受けるようになり、問題が起きたときにより早く修正し、改善点にも積極的に飛び込めるようになる
要点はリファクタリングがトークン消費を減らすということであり、抽象論に終わらず効果を定量化した点はよい
ただしFowlerは『Refactoring』で、リファクタリングの必須前提条件は堅牢なテストだと述べており、AIとは無関係に本当の利点はここにあると思う
良いテストは人間やロボットが作った回帰を防ぎ、両者が読める仕様をコードとして残す: https://www.oreilly.com/library/view/refactoring-improving-the/9780134757681/
Sonnet 5の価格をMTokあたり3ドルで計算すると、データアクセス層に触れる今後の変更で節約される金額は39.7セントである
OpenAIの値下げやオープンモデル、長期的なトークン価格の下落を考えると、時給100ドル程度のシニア開発者がリファクタリングを誘導するコストに見合わないかもしれない
エージェントが作ったコードは、エージェントだけが読んで理解できるほど巨大な塊になっており、これが機能なのかバグなのか創発的特性なのかよりも、現実そのもののほうが重要になっている
AIツールで生成したコードを扱うために、再び AIツールに依存するようになっている
ただし、人間もすでにひどく巨大なファイルやモノレポを作ってきたし、LLMのおかげでそれらを編集しリファクタリングすることがようやく現実的になった
コードベースが人間には理解するには大きすぎ、しかもひどく散らかった状況では、LLMが私たちを救うかもしれず、個人的には巨大ファイルは嫌いだが、Fowler式の整理原則はもはや重要ではないのかもしれず、複雑な気分になる
平均的、あるいは時々うまくやる程度の社員ですら、以前は会社の手続きのせいでできなかったことを電光石火で実行できるようになり、かえって悪い社員に変えてしまうことがある
エージェントが巨大なファイルや関数を作るなら、そうするなと指示するだけで従う
リファクタリングは健全な開発チーム を示す最良の兆候の一つだと思う
リファクタリング自体に利点はあるが、プロダクトオーナーや機能バックログではその価値が見えにくい
チームがソフトウェア全体の健全性のためにリファクタリングしているなら、それは開発者が良いソフトウェアのための提案を気軽に出せて、その提案が真剣に受け止められていることを意味する
ソフトウェアの腐敗は、チームに高品質なソフトウェアというビジョンを実現する動機や権限がないときに最も深刻になり、チームが卓越性についての判断に従えるなら、たいていは良い兆候である
もちろん、Ruby、Node、Rustを経て、再びエージェントフレンドリーな技術へ全面書き換えするような行き過ぎもあるが、企業環境では改善の許可がないと感じているチームのほうがはるかに一般的である