1 ポイント 投稿者 GN⁺ 2 시간 전 | 1件のコメント | WhatsAppで共有
  • エージェントが作成した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 トークンへ急減した
  • 最終的なデータアクセス層は 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 はクライアントクラス全体の抽出まで特定した
  • 機械的な変更には grepsed を使う 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 コレクションに itemIduserIdcreatedAt フィールドとして保存する
  • 別個の Rust レコード構造体は使わず、item ID の Vec<String> を返す
  • FakeStore にはメモリ内の Vec<(String, String)> フィールドを追加し、FirestoreStore には既存の HTTP パターンをそのまま使って実装する
  • 応答の末尾には、読んだファイルと文字数、応答文字数を JSON で出力させ、コードはコミットしないよう指示した

適用したリファクタリング計画

  • 1 段階目 — FirestoreClient クラス抽出

    • ドメインクエリの調整と Firestore HTTP 送信の責務を分離する
    • reqwest::Clientproject_idMetadataAuth、URL・認証ヘッダー処理を新しい構造体へ移す
    • 計画上は FirestoreStore 実装から約 1,200 行を減らし、クライアントに約 120 行を追加する
  • 2 段階目 — extract_doc_idnew_link 関数抽出

    • 20 個のドキュメントパーサーにおける ID 抽出と、62 回繰り返された Link 生成を共通化する
    • 約 500 行の削減を見込む
  • 3 段階目 — リンククエリパイプライン関数抽出

    • 約 15 か所のクエリ結果収集と、約 8 か所の単一対象 ID 参照パターンを共通化する
    • 約 200 行の削減を見込む
  • 4 段階目 — FakeStoreInner リンク条件関数抽出

    • 約 15 個のメソッドで繰り返されていた inner.links.iter() の変形を 2 つのメソッドに分離する
    • 約 120 行の削減を見込む
  • 5 段階目 — Firestore 値生成関数の導入

    • 128 回以上繰り返された文字列・タイムスタンプなどの json! 表現を 4 つの関数呼び出しに置き換える
    • 複数行マクロを 1 行の呼び出しに変え、約 80 行の削減を見込む
  • 6 段階目 — FieldsBuilder 抽出

    • 約 20 個のエンコーダーにおけるフィールドマップ生成パターンをビルダーに統合する
    • 約 40 行のエンコーダーを約 12 行に減らし、合計 500〜600 行の削減を見込む
  • 7 段階目 — queries.rs 分離

    • 32 個の LinkQuery 定数と関連型を別モジュールへ移動する
    • 既存の呼び出し側を変更せず、mod.rs から約 800 行を減らす
  • 8 段階目 — traits.rs 分離

    • 17 個の公開トレイトと関連エラー型を移動し、再エクスポートする
    • mod.rs から約 1,900 行を減らすが、新しいファイルも約 1,900 行になる
  • 9 段階目 — traits/ のドメイン別分離

    • トレイトを planning.rscontent.rspeople.rssystem.rs に分ける
    • 定義と呼び出し側を変えず、ファイルごとのサイズを約 300〜650 行に抑える
  • 10 段階目 — codec.rs 分離

    • ドキュメントエンコーダー・デコーダー、パーサー、FieldsBuilder、値生成関数を移動する
    • 6 段階目以降は約 400〜500 行のモジュールとなり、mod.rs から約 500 行を減らす
  • 11 段階目 — fake_store.rs 分離

    • FakeStoreFakeStoreInner、18 個のトレイト実装を移動する
    • mod.rs から約 4,700 行を減らす
  • 12 段階目 — FirestoreStore 実装の分離

    • store/mod.rs には構造体、コンストラクタ、FirestoreClientMetadataAuth を置き、トレイト実装はドメイン別ファイルに分ける
    • 約 10,000 行のファイルを、それぞれ 120〜650 行の 10 個のファイルに変え、mod.rs は約 100 行の再エクスポートファイルにする
  • 13 段階目 — テストを対象モジュールと同じ場所に配置

    • テストコードは変更せず、各実装ファイルの下へ移動する
    • mod.rs から約 2,000 行を減らし、各ファイルには関連テスト 200〜700 行が追加される

限界と今後の実験

  • リファクタリング計画の作成と実行にかかったトークンを別途数えていないため、リファクタリング投資コストを正確に計算できない
  • 該当時間帯の全使用量を基準にした上限は 500 万トークンだが、ここには計画を 2 回作成した作業、実験および代表的変更の設計、その他の作業がすべて含まれる
  • 実験対象はまだグリーンフィールド段階であり、1 人の開発者が構築・管理する 1 つの大規模アプリケーションであるため、結果を一般化することはできない
  • 今後の作業として、正確なリファクタリングトークンの測定、より複雑な変更、より広範囲のリファクタリング、継続的なリファクタリング、アプローチ別の相対的価値比較が必要である
  • 今回の実験は、リファクタリングで得られる時間的・金銭的価値とリファクタリング自体のコストを併せて測定するための出発点となる

1件のコメント

 
GN⁺ 2 시간 전
Hacker News の意見
  • ほとんどのIT企業が無視してきた開発者のベストプラクティスが、AIのベストプラクティスとして再発明されているのが面白い
    以前は、コードの中にドキュメントを置き、Jiraのタスクだけを投げるのではなくプロジェクト全体の文脈を共有し、長期的な生産性のためにリファクタリングしようと言っても退屈がられていた
    今では同じ内容でも、AI向けドキュメントをコードやCLAUDE.mdに置き、プロンプトで細かく制御しすぎず、AIの生産性のためにリファクタリングしようと言うと興味深く受け止められる

    • 人間の同僚よりも、AIエージェントに同じ仕事を一貫して実行させるほうがはるかに簡単
      人間は正しいやり方を分かっていても忙しかったり集中力が切れたりするが、エージェントは退屈な作業にうんざりしないので、人間に有効だと証明されながら継続適用が難しかった手順が現実的に可能になる
      共通仕様に基づいて実装とテストを別々のエージェントに書かせ、監査エージェントに検証させて互いの結果に汚染されないようにしたが、これはIBMが1980年代に人間向けに開発したクリーンルーム工学をAIで広範かつ一貫して適用したものだ
    • この記事に関係する人物は、20年以上前に『Refactoring』を書いてその用語を広めたMartin Fowler
      古い慣行をAIブームに合わせて新しいもののように包装しているのではなく、20年以上前のベストプラクティスが今でも有効だという点を根拠つきで示している
    • AIの前後での大きな違いは、人間にはかなり優れた長期的なコンテキスト管理能力があること
      エージェントはセッションごとに文脈を学び直さなければならないため、ベストプラクティスの価値がはるかに高まり、効果もすぐに現れる
    • AIブームのおかげで、望んでいた開発者体験の改善作業に予算がつくようになったが、その理由が間違っているのはほろ苦い
      それでも、1時間のあいだにCLIを100回実行して新しいフラグの使い勝手を試せるのは良い
    • 人間は、SharePointの古い文書、会議中に小耳にはさんだ全体文脈、低いリファクタリング優先度の中でも、品質と納期が悪化するだけで何とか結果を出してしまう
      一方AIは、こうした基盤がなければ非常にひどい働きしかできないか、まったく動かないので、健全なエンジニアリング慣行は長期的な改善策ではなく必須の前提条件になる
      実際の純効果がまったくなくても、AIをワークフローに入れることで、きちんとした開発慣行を導入する口実ができるという点では有用だ
  • この記事は、AIツールが実際にどう使われているかに基づいて具体的かつ定量的に批判している点が良い
    実際の利用事例もなく社会的リスクを漠然と論じる記事より、AIに何ができないのかを測定値で示す記事のほうがはるかに有益だ
    同じ理由で、Boko Haramの構成員にインタビューしてAIがテロにどう活用されたかを調査した報告書も印象的だった

  • AIを使わず自分でやるリファクタリングが本当に好き
    見た目の変化はないが、今すぐ成果が見えないWebサイトを将来ずっと扱いやすくすることに満足感がある
    既に確立されたパターンで解決済みの問題を、昔の奇妙な回り道コードがもう一度解こうとしている箇所を見つけて、ベストプラクティス側へ移しつつ新たな技術的負債を作らない過程がパズルのようで楽しい
    認証に至るまですべてを雑に自前実装して難しいやり方で内部原理を学び、その結果として今後10年楽しめるリファクタリングの種も手に入った

    • Fowlerの『Refactoring』を読んで、科学・研究用コードにも効果があるのか疑っていたが、気に入らないコード構造に試しに適用したあとで見方が完全に変わった
      コードベースはひとつのシステムという言葉を具体的に理解できるようになり、コードを引っ張ったり押したりできる連続的な構造や網のようなものとして、高いレベルから見られるようになった
      AIがこうした学習過程をなくしてしまえる点が、ジュニア開発者の問題を浮き彫りにしている。直感を得るには自分で深く掘り下げるしかなく、Naurが40年前に警告していても、この教訓は繰り返し忘れられる
    • Windows 98のデフラグ画面を見ているときのようなドーパミン報酬があるからかもしれない
    • その感情は職人としての誇りだ。理解できる人には説明は不要で、理解できない人にはどんな説明も通じない
    • リファクタリング中の回帰を防ぐために、どの程度テストスイートを安全装置として整備していたのか気になる
    • さまざまなリファクタリングパターンと実際の適用事例を学ぶ過程が楽しい
  • エージェントがリファクタリングする際には、人間の関与が不可欠だと考える
    生成モデルが初期作業に集中して見落とした部分をレビュー用モデルが見つけられることはあるが、プロジェクト全体の目的とコードがどう結びつくかを実際に理解し、重複やより洗練された構造を見分けられるかは疑問である
    コーディングエージェントにリファクタリングを任せるのは、外傷外科医に運動能力を高めてほしいと頼むようなもので、きちんとやるには全体論的な視点が必要である
    大きなファイルを複数のファイルに分けるだけでは表面的なリファクタリングにとどまる。どのコードを一緒に置くべきか、何をユーティリティ関数として抽出すべきかという理論がなければ、因数分解ではなく大きな数を小さな数に分けてからまた足し直すのに近い
    エージェントは、APIですでに取得している値を再び保存して計算するシステムを作ることもあるが、人間はプロジェクト全体を見渡して、JSONの特定のキーに必要なデータがすでにあることを正確に見つけ出せる

    • 現在のLLMも、特定のコード区間に対して具体的なリファクタリングを指示すれば、十分うまく実行できる。たとえば、データクラスの代わりに functools.partialCommandパターン を実装しろという複雑な要求にも対応可能である
      ファイル境界は論理的なサブシステムの境界を表して推論を容易にし、他ファイルの内容は基本的に不透明なものとして扱う、という概念も学習データに豊富に含まれている
      この構造の利点は、人間の認知の偶然的な特性だけではなく客観的な側面もあると思う
    • 人間が横で方向性を説明する形で、数か月かかるリファクタリングを約1週間でほぼ終わらせた
      リファクタリング経験や他人のコードベースを扱ってきたやり方、元の設計者として過去と現在の意図を説明できた点が役立った
      依存関係の脆弱性が少なく、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/

    • この記事はmartinfowler.comに載っているが、Martinが書いたものではなく、著者はThoughtworks CTOのGiles Edwards-Alexanderと表示されている
    • 実際の核心は、節約額がたかだか数十セント程度だという点である
      Sonnet 5の価格をMTokあたり3ドルで計算すると、データアクセス層に触れる今後の変更で節約される金額は39.7セントである
      OpenAIの値下げやオープンモデル、長期的なトークン価格の下落を考えると、時給100ドル程度のシニア開発者がリファクタリングを誘導するコストに見合わないかもしれない
  • エージェントが作ったコードは、エージェントだけが読んで理解できるほど巨大な塊になっており、これが機能なのかバグなのか創発的特性なのかよりも、現実そのもののほうが重要になっている
    AIツールで生成したコードを扱うために、再び AIツールに依存するようになっている
    ただし、人間もすでにひどく巨大なファイルやモノレポを作ってきたし、LLMのおかげでそれらを編集しリファクタリングすることがようやく現実的になった
    コードベースが人間には理解するには大きすぎ、しかもひどく散らかった状況では、LLMが私たちを救うかもしれず、個人的には巨大ファイルは嫌いだが、Fowler式の整理原則はもはや重要ではないのかもしれず、複雑な気分になる

    • エージェントのコードはエージェントしか理解できない、というのは根本的に事実ではなく、そう感じるなら LLMの使い方 が間違っている
    • 悪いコードはもともと存在していたが、AIは 悪い採用の被害範囲 を1,000倍に広げる新しい問題である
      平均的、あるいは時々うまくやる程度の社員ですら、以前は会社の手続きのせいでできなかったことを電光石火で実行できるようになり、かえって悪い社員に変えてしまうことがある
    • LLMが100%書いたコードベースでも、読むことや目的の箇所を探すことに問題はなく、自分で書いた場合より難しいことはなかった
    • エージェントが作ったコードが、人間が作った最悪のコードよりひどいケースは見たことがない
      エージェントが巨大なファイルや関数を作るなら、そうするなと指示するだけで従う
  • リファクタリングは健全な開発チーム を示す最良の兆候の一つだと思う
    リファクタリング自体に利点はあるが、プロダクトオーナーや機能バックログではその価値が見えにくい
    チームがソフトウェア全体の健全性のためにリファクタリングしているなら、それは開発者が良いソフトウェアのための提案を気軽に出せて、その提案が真剣に受け止められていることを意味する
    ソフトウェアの腐敗は、チームに高品質なソフトウェアというビジョンを実現する動機や権限がないときに最も深刻になり、チームが卓越性についての判断に従えるなら、たいていは良い兆候である
    もちろん、Ruby、Node、Rustを経て、再びエージェントフレンドリーな技術へ全面書き換えするような行き過ぎもあるが、企業環境では改善の許可がないと感じているチームのほうがはるかに一般的である