Skip to content
Go back

重构能省多少 Token?一次 Agent 代码库的经济实验

Giles Edwards-Alexander 是 Thoughtworks 欧洲、中东和印度区的 CTO。为了摸清 agentic engineering 是怎么回事,他用 AI agent 写了一个正经的应用程序来辅助日常工作。

这个应用不算小:高质量的 Web UI,带动态刷新、模态框和自动保存,集成外部系统,还有机器学习、文本分析、后台任务和全自动部署。代码总量大约 15 万行,主力语言是 Rust(约 12 万行),其余是 TypeScript 和 Terraform。

整段代码全由 agent 编写,主要是 Claude Code,少量用了 Cursor。作者没有逐行 review,顶多出于好奇偶尔翻翻。

一个 17,155 行的文件

写的过程中,他发现事情不太对。一次无意中看到终端里滚动到某个文件第 4000 行的编辑操作,他凑近看了一眼。数据访问层已经涨到了 6000 多行。随着新功能不断增加,这个文件还在涨——每个查询、每个读写操作都重复着同样的 HTTP 请求设置、同样的 JSON 编码解码。最终,它来到了 17,155 行。一个 Rust 文件。

这个文件就是整个数据访问层,一个自包含的模块。仔细看下来:没有去重、没有内部 DSL、函数提取很少、几乎没有类的提取。但它有一个清晰的边界和需要保留的接口——这让它成了重构的绝佳目标。

实验设计:Agent 不会学习,正好做对照

通常我们重构是为了让代码更好维护。但对 agent 写的代码来说,重构有一个更直接的经济目标:现在花 token 去重构,让未来的每一次修改消耗更少的 token

Agent 有一个人类工程师没有的特性:它们不会从经验中学习。每次调用都是全新的。这意味着你可以设计一个干净的对照实验——在每个重构步骤之后,用一个全新的 sub-agent 去执行完全相同的修改任务,测量它的 token 消耗。不会有人类实验中那种「被试已经熟悉了代码」的干扰。

实验流程是这样的:

  1. 制定整体重构计划,严格遵守重构纪律
  2. 设计一个有代表性的修改任务,写成单个 prompt
  3. 在 sub-agent 中执行这个 prompt,获取 token 消耗作为基线
  4. 丢弃这次修改
  5. 循环执行:应用一步重构 → 用新 sub-agent 执行同样的修改 → 记录 token 消耗 → 丢弃修改
  6. 记录所有数据:token 成本、修改耗时、每个重构步骤后的代码行数

有一个小遗憾:Claude 当时无法可靠地实时统计 token 数量。所以 sub-agent 改为报告接收和发送的字符数,再用 tiktoken 近似换算(字符数除以 4)。

结果:不是代码变短了,是 Agent 读得少

15 步重构的数据如下表:

步骤数据访问层总行数最大单文件行数输入 Token输出 Token耗时(秒)
Baseline17,15517,155159,5641,705342
Step 1-6~16,500~16,500~155k-171k~1,700-2,100~500-1,350
Step 7-11~16,50015,670→11,122151,850→131,871~1,700-2,460~540-600
Step 12 (store/ split)16,5509,269104,0802,050490
Step 14-1516,553→16,6087,225→3,695107,205→27,360~2,100-2,450~450-520

最值得关注的是三个指标:数据访问层的总行数、最大单文件行数、以及执行代表性修改时的输入 token 消耗

从基线到最终重构步骤,相同修改任务的输入 token 从 159,564 降到了 27,360,节省了 132,204 个 token,降幅 83%

而且这不是一次性节省。从此以后,每一次触及数据访问层的修改,成本都显著降低了。

这节省来自哪里?agent 需要读取的代码变少了。但有意思的是,总体代码量并没有减少——数据访问层的总行数从 ~17,155 变成了 ~16,608,几乎持平,只是从 1 个文件变成了 19 个 Rust 文件。

能兑现这个节省的前提是:agent 能够成功地识别出完成任务需要读取的最小文件子集。从 Claude Code 的思考输出和文件读取摘要来看,sub-agent 每次确实在读取越来越小的代码片段。

关键洞察:随机拆分文件没用

有一个容易产生的误解值得澄清:随机把大文件切小并不能达到这个效果

即使每个文件都变小了,agent 仍然会被迫在大量文件之间来回跳转来寻找相关代码。最大的降幅出现在最后几步——当文件被拆分成更小的模块之前,前面十几步重构一直在做的是:提取客户端类、提取函数、提取查询辅助方法、建立值对象构造器、分离 trait 和编解码器。

这正是典型重构的自然节奏:先在单个文件内部消除重复、建立内部语言,等一个可复用的核心浮现出来之后,再把它拆成更小的文件。

这也意味着,如果只是粗暴地让 agent「把这个文件拆成 20 个」,不去花时间建立抽象,大概率是省不了多少 token 的。

省了多少?算术上不多,但乘法还没开始

按写作时 Sonnet 5 的 $3/MTok 定价来算,每次修改节省 13.2 万 input token,约等于 39.7 美分。单个修改看起来微不足道。

但这个东西的威力应该在乘法上。debug 的时候呢?更复杂的功能修改呢?如果不只重构数据访问层,而是激进地重构整个代码库,又能找到多少节省空间?这些重构本身又要花多少 token?

作者坦承这次实验没有记录执行重构计划所消耗的 token。事后回溯整个时间窗口的用量,上界大约是 500 万 token——这包括两次制定重构计划、设计实验和代表修改任务、以及各种其他相关工作。未来需要更精确地测量「重构本身的成本」和「重构带来的长期节省」之间的关系。

Claude 不擅长重构

实验过程中有一个诚实的观察:Claude 在重构这件事上表现不好。

如果你去看下面的 prompt 和重构步骤(原文末尾有附录),很明显每一步重构都是在 prompt 直接指导下产生的。Claude 还不具备「看代码 → 判断哪些重构方法适用 → 自主选择」的能力。开发者需要在重构过程中主动引导它。

更有意思的是,Claude.ai 的表现比 Claude Code 更好。作者同时用两个界面来制定重构计划:Claude Code 能看出「提取函数」作为第一步,但 Claude.ai 走得更远,能看到整个客户端类需要被提出。

在机械执行方面也不行。重构的实际执行是通过写 Python 脚本用 grep 和 sed 来完成的,这些脚本常常被缩进搞晕。最有价值的一次重构(把 trait 拆到独立文件)在第一轮中被遗漏了,需要后续重新应用。

整个实验大约花了 8 小时,大部分时间无人值守。唯一一次人工介入是在 6 小时 40 分之后——agent 跳过了那个关键步骤,需要被重新定向。

这只是一个开始

这是单个实验,在一个特定类型的应用上(单人开发维护的绿地项目)。但它给出了一个值得关注的方向:在 agentic 开发模式下,重构的经济价值可以被量化、被实验验证

几个值得继续探索的问题:

把一个 17,155 行的怪物文件拆成 19 个整洁的小文件,结果同一任务的输入 token 降了 83%。这不是代码审美的胜利,这是一个可以复现的经济决策依据。

如果你关注 AI 助手、开发工具和软件工程实践,可以关注 Aide Hub。这里会继续分享能落地的工具教程、技术观察和项目经验。

参考


Tags


Next

ChatGPT Agent 循环优化:Harness、API 与推理层的三层提效实践