プログラミング
リファクタリングの経済的メリット
The Economic Benefit of Refactoring (martinfowler.com)
要約
この記事は、AIエージェントによって生成された大規模なコードベースにおけるリファクタリングの経済的メリットを検証しています。大規模なデータアクセス層をリファクタリングした結果、同じタスクを実行するためのトークン消費量が83%削減され、将来的な開発コストの低減につながることが示されました。これは、エージェントが過去の学習に依存せず、毎回同じタスクを再実行できるため、リファクタリングの効果を実験的に測定できたことによります。
全文翻訳
リファクタリングの経済的メリット
Giles Edwards-Alexander
GilesはThoughtworksのヨーロッパ、中東、インド担当CTOです。彼はモバイルからAIまで、また小売、フィンテック、ヘルスケアなどの業界で、25年以上にわたりエンジニアリングとテクノロジーリーダーシップの経験を持っています。この記事は「Gen AIの探求」シリーズの一部です。このシリーズは、ソフトウェア開発におけるGen AI技術の活用に関するThoughtworksの技術者たちの探求を記録しています。
2026年7月30日
エージェントエンジニアリングという新しい世界を理解するため、私は自分の仕事(をサポートする)アプリケーションを構築しました。それは洗練されたアプリです。ダイナミックなリフレッシュとルックアップ、モーダルと自動保存を備えた高品質なWeb UI、外部システムとの統合、機械学習とテキスト分析、バックグラウンドジョブ、そして完全に自動化されたデプロイメントを備えた適切な環境セットアップ。コード量は約15万行で、主にRust(約120キロロク)で、残りはTypeScriptとTerraformです。これは完全にエージェントによって書かれました。主にClaude Codeを使用し、Cursorも一部使用しました。興味本位で時折見る以外は、コードを読んだりレビューしたりしませんでした。
アプリケーションを構築している間、いくつかの問題が発生していることに気づきました。ターミナルでファイルのある行(4000行目)への編集が流れるのを見た後、詳しく見てみました。データアクセス層は6000行以上にまで成長していました。機能が増えるにつれて、これは成長し続けました。すべてのクエリ、読み取りまたは書き込みで、同じHTTPリクエスト設定、同じJSONエンコーディングとデコーディングが繰り返されました。最終的に、それは17,155行に達しました。単一のRustファイルの中に。
リファクタリングの実験
17,155行のファイルは、データアクセス層全体でした。単一の、自己完結したモジュールです。コードをレビューしたところ、重複排除、内部言語、関数の抽出が限定的、クラスの抽出が非常に少ないという状態でした。インターフェースを維持するという明確な境界はありました。リファクタリングの素晴らしいターゲットでした。
エージェントコードベースをリファクタリングする目標は、将来の作業のトークン消費量を減らすために、現時点でトークンをリファクタリングに費やすことです。リファクタリングが進むにつれて、このコードベースでの個別の機能実装のトークンコストが減少することを示す実験ができるはずです。エージェントは決して学習しないため、これが実験として実行可能になりました。リファクタリングの各段階の後で、全く同じ変更を行うように、新鮮なエージェントにプロンプトを与えることができました。人間のエンジニアとは異なり、実験は以前のステップからの学習によって汚染されません。
厳格なリファクタリング規律に従って、全体のリファクタリング計画を作成します。単一のプロンプトで記述された代表的な変更を作成します。変更のベースラインコストを確立します。サブエージェントで、そのプロンプトを実行し、変更のトークン消費量を報告するように依頼します。変更を破棄します。ループで:全体のリファクタリングの単一ステップを適用します。サブエージェントで、変更のトークンコストを受け取りながら、全く同じ変更を実行します。変更を破棄します。リファクタリングの各ステップの後、ベースラインを含めて、すべてのトークンコスト、変更実行時間、およびコード行数を記録します。代表的な変更に使用されたプロンプトと適用されたリファクタリングステップは、以下の付録に示されています。
1つの注意点:Claudeは、トークン数を表示し、セッションあたりのトークン消費量を報告し、トークンに対して請求するにもかかわらず、ライブでトークンを数える信頼できる方法を提供していません。これは一時的な問題であり、時間とともに改善されると想定しています。代わりに、サブエージェントは送受信した文字数を報告し、文字数を4で割ることでトークンを近似するためにtiktokenを使用しました。
結果
| ステップ | データアクセス層 LoC | 最大ファイル LoC | 総Rust LoC | 変更あたりの入力トークン | 変更あたりの出力トークン | 変更あたりの時間(秒) |
|---|---|---|---|---|---|---|
| ベースライン | 17,155 | 17,155 | 50,359 | 159,564 | 1,705 | 342 |
| ステップ 1 (FirestoreClient) | 16,706 | 16,706 | 49,910 | 155,205 | 1,723 | 530 |
| ステップ 2 (extract_doc_id, new_link) | 16,562 | 16,562 | 49,766 | 159,227 | 2,105 | 574 |
| ステップ 3 (link-query helpers) | 16,567 | 16,567 | 49,771 | 154,054 | 2,105 | 524 |
| ステップ 4 (FakeStore predicates) | 16,577 | 16,577 | 49,781 | 154,146 | 2,060 | 654 |
| ステップ 5 (value ctors) | 16,469 | 16,469 | 49,673 | 171,251 | 2,036 | 1,353 |
| ステップ 6 (FieldsBuilder) | 16,469 | 16,469 | 49,673 | 171,251 | 2,036 | 1,353 |
| ステップ 7 (queries.rs) | 16,474 | 15,670 | 49,678 | 151,850 | 1,800 | 587 |
| ステップ 8 (traits.rs) | 16,508 | 13,845 | 49,712 | 132,558 | 1,723 | 446 |
| ステップ 9 (traits/ split) | 16,508 | 13,845 | 49,712 | 132,558 | 1,723 | 446 |
| ステップ 10 (codec.rs) | 16,521 | 12,846 | 49,725 | 131,871 | 1,750 | 540 |
| ステップ 11 (fake_store.rs) | 16,535 | 11,122 | 49,739 | 133,016 | 2,460 | 600 |
| ステップ 12 (store/ split) | 16,550 | 9,269 | 49,754 | 104,080 | 2,050 | 490 |
| ステップ 13 (co-locate tests) | 16,550 | 9,269 | 49,754 | 104,080 | 2,050 | 490 |
| ステップ 14 (complete fake_store.rs) | 16,553 | 7,225 | 49,757 | 107,205 | 2,453 | 523 |
| ステップ 15 (store/ split) | 16,608 | 3,695 | 49,812 | 27,360 | 2,113 | 454 |
ここで興味深いメトリクスは、データアクセス層の総コード行数、データアクセス層の単一の最大ファイル内のコード行数、および変更の生成中に消費された入力トークン数です。このグラフは4つのことを示しています。最初の点はベースライン(ステップ0)であり、その後、各リファクタリングステップが適用された後に同じメトリクスが繰り返されます。データアクセス層全体の総コード行数。当初は、私が開始した単一のファイルだけでした。リファクタリングが適用されるにつれて、これは多くのファイルになります。最終的には19個のRustファイルになります。単一の最大ファイル内のコード行数。これは、単一の初期ファイルにあったデータレイヤー全体から始まりました。最終的には、単一の最大ファイルはテストライブラリです。さらなるリファクタリングパスは、これに同じアプローチを適用できます。サブエージェントが代表的な変更を適用中に消費した総入力トークン数。サブエージェントが生成した総出力トークン数。
リファクタリングはトークン消費量を削減します
結果は明らかです。入力トークンは、最大ファイルが減少し始めるまでほぼ横ばいですが、その後、Claudeの言葉を借りれば、崖から落ちるように減少します。ベースラインと最終リファクタリングの間で、同じタスクの入力トークンは159,564から27,360に減少しました。これは132,204トークン、つまり83%の節約です。そして、その節約は一度きりではありません。この時点からデータアクセス層に触れるすべての変更は、大幅にコストが低くなります。どれだけの節約か?執筆時点でのSonnet 3.5の価格を100万トークンあたり3ドルと仮定すると、39.7セントです。それほど多くはありません。これは倍増するでしょうか?デバッグではどうなるでしょうか?より複雑な機能では?これはコードベースの1つの部分のみのリファクタリングですが、コードベース全体を積極的にリファクタリングして、どこでも節約を見つけることができるでしょうか?それらのリファクタリングにはどれくらいの費用がかかるでしょうか?この節約は、エージェントが読むコードが少なくなるためです。しかし、読むべきコードが少なくなるからではありません。データアクセス層全体のコードはほぼ一定のままです。したがって、この節約を確保するためには、エージェントは読む必要がある最小限のファイルセットを特定できる必要があります。結果は、それが起こっているように見えます。変更が適用されるにつれて、Claude Codeの思考出力とファイル読み取りの要約を読むと、サブエージェントが毎回ますます小さいコードセクションを読み取っていることが示唆されます。言い換えれば、ファイルをランダムに小さなファイルに分割しても、それほど効果はないでしょう。各ファイルが小さくても、エージェントは関連コードを探して多くのファイルを読み取ることを余儀なくされるでしょう。最も効果があったステップは最後ですが、以前のステップは、この節約を設定するためのリファクタリングでした。これは計画されたものではありませんでした。それは単に、リファクタリングが通常どのように進行するかという結果でした。繰り返しのコアが出現したら、それをより小さなファイルに分割する前に、重複を抽出するためのローカルファイル変更が行われました。リファクタリングは代表的な変更を小さくしませんでした。コードを書く際に生成されるトークン数はほとんど影響を受けませんでした。出力トークンはあまり動きません。それらのトークンは入力トークンの5倍の価格です。しかし、それらはずっと少ないです。適用できるリファクタリングはありますか?