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 消耗。不会有人类实验中那种「被试已经熟悉了代码」的干扰。
实验流程是这样的:
- 制定整体重构计划,严格遵守重构纪律
- 设计一个有代表性的修改任务,写成单个 prompt
- 在 sub-agent 中执行这个 prompt,获取 token 消耗作为基线
- 丢弃这次修改
- 循环执行:应用一步重构 → 用新 sub-agent 执行同样的修改 → 记录 token 消耗 → 丢弃修改
- 记录所有数据:token 成本、修改耗时、每个重构步骤后的代码行数
有一个小遗憾:Claude 当时无法可靠地实时统计 token 数量。所以 sub-agent 改为报告接收和发送的字符数,再用 tiktoken 近似换算(字符数除以 4)。
结果:不是代码变短了,是 Agent 读得少
15 步重构的数据如下表:
| 步骤 | 数据访问层总行数 | 最大单文件行数 | 输入 Token | 输出 Token | 耗时(秒) |
|---|---|---|---|---|---|
| Baseline | 17,155 | 17,155 | 159,564 | 1,705 | 342 |
| Step 1-6 | ~16,500 | ~16,500 | ~155k-171k | ~1,700-2,100 | ~500-1,350 |
| Step 7-11 | ~16,500 | 15,670→11,122 | 151,850→131,871 | ~1,700-2,460 | ~540-600 |
| Step 12 (store/ split) | 16,550 | 9,269 | 104,080 | 2,050 | 490 |
| Step 14-15 | 16,553→16,608 | 7,225→3,695 | 107,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 开发模式下,重构的经济价值可以被量化、被实验验证。
几个值得继续探索的问题:
- 更复杂的修改任务(跨多层、跨多模块)是否也有类似效果?
- 不同类型的重构(函数提取 vs 类提取 vs 模块拆分)产生的节省是否有显著差异?
- 如果能更准确地跟踪「重构成本」和「后续节省」,是否可以在 Agent 开发流程中集成持续重构?类似 CI 中的 lint 和测试,但目标是 token 成本?
- 输出 token(生成代码的部分)基本没有变化。是否存在某种重构,能同时减少输出 token 的消耗?
把一个 17,155 行的怪物文件拆成 19 个整洁的小文件,结果同一任务的输入 token 降了 83%。这不是代码审美的胜利,这是一个可以复现的经济决策依据。
如果你关注 AI 助手、开发工具和软件工程实践,可以关注 Aide Hub。这里会继续分享能落地的工具教程、技术观察和项目经验。