这是最近 Matt 和 Uncle Bob 的一次直播连线,两人聊的话题也是我自己比较关心的,以视频内容为基础写成了文章,分享给大家!感兴趣的建议去看原视频。

00
先认识访谈中的两个人
Matt Pocock 是一名开发者教育者、内容创作者和工程师。他创办了 Total TypeScript,也做过 Vercel 的开发者布道师。现在,他把工作重心转向 AI Engineering,他在 Github 上开源的 Skills 几乎是目前最受欢迎的 AI Coding 技能合集了。
Robert C. Martin 常被开发者称为 Uncle Bob。他在 1964 年写下第一个程序,当时只有 12 岁。他从 1969 年开始从事职业编程,后来写下《Clean Code》《The Clean Coder》和《Clean Architecture》 一系列经典书籍,也在长期参与敏捷开发与软件工艺实践。
01
Agent 写得越快,烂代码堆得也越快
Uncle Bob 最初使用 Agent 时并不满意。
他让一个早期的 Grok Agent 帮忙开发手头的项目。代码确实很快写出来了,质量却很差。他不得不不断跟在后面清理。用他自己的说法,Agent 到处留下”dog doo”。
dog doo
这是 Uncle Bob 在访谈中的戏谑说法,直译接近”狗屎”。它指 Agent 为了让功能尽快跑起来而留下的混乱代码,例如重复逻辑、过长函数、随意依赖和缺少测试。它不是正式的软件工程术语。
速度本来应该提高效率,结果却让他变慢。
Matt 追问了一个很多开发者都会问的问题。既然 Agent 修改代码这么快,为什么还要在意代码是否整洁?能不能继续往前冲,让它把 Bug 一个个修掉?
Uncle Bob 观察到,Agent 和人一样,也会被混乱的代码拖垮。
当他不清理前一次任务留下的问题,直接让 Agent 继续开发,代码里的混乱会逐步累积。随后,Agent 开始来回折腾。它修改一个地方,破坏另一个地方。修复第二个问题时,又把第一个问题带回来。最后,它陷入循环,甚至放弃任务。
Agent 能忍受的复杂度也许比人高,但它同样有上限。坏代码的问题不只在于人看不懂。它也会降低 Agent 后续修改代码的能力。
于是 Uncle Bob 把目光转向两种诞生已久、过去却很难持续执行的工具。
一种是 CRAP 分析。它把测试覆盖率和函数的圈复杂度放进一个公式,计算代码的风险分数。Uncle Bob 在 21 世纪初曾用它检查一个大型项目。工具找出了许多高风险函数,但逐个重构、补测试太费时间,他最终放弃了。
CRAP 分析
CRAP 是一种代码风险评分方法。它综合函数的圈复杂度和测试覆盖率。分支多、测试少的函数得分更高,也更值得优先重构。
测试覆盖率
测试运行时实际执行了多少代码。覆盖率高不代表测试一定有效,但覆盖率低通常意味着有些路径从未接受自动化检查。
圈复杂度
代码中独立执行路径的数量。
if、循环和其他分支越多,圈复杂度通常越高,理解和测试这个函数也越困难。
另一种是变异测试。工具会故意修改源码,例如把小于号换成大于号,把等号换成不等号,然后重新运行测试。如果测试仍然通过,就说明测试没有发现这次错误修改,这个”变异体”存活了。
变异测试
工具主动对代码做小幅错误修改,再运行测试。如果测试没有失败,说明现有测试没有真正约束这段行为。变异测试检查测试能不能抓住错误,测试覆盖率只说明代码有没有被执行。
当时,他的测试套件运行一次需要 4 分钟。变异测试要把它重复运行几百次,只能整夜执行。工具能找到问题,却很难进入日常开发流程。
Agent 改变了这里的成本结构。它不怕工作无聊,也能连续修改代码。Uncle Bob 让 Agent 跑 CRAP 分析,降低复杂度,再运行变异测试,补上没有杀死变异体的测试。过去一次变异测试要整夜运行,现在大约半小时能跑完,后续修补也可以继续交给 Agent。
Agent 的速度没有让质量工具过时。恰恰是这份速度,让一些过去成本太高的工程做法终于可以落地。
02
少写几页规则,多设置一道过不了的检查
许多人看到 Agent 写出坏代码,第一反应是继续补提示词。
这也是 Uncle Bob 最初的做法。他在提示词里解释测试驱动开发,列出 Clean Code 规则,告诉 Agent 代码应该长什么样。规则越来越多,最后可以写满五到十页。
效果并不好。
他借用了《加勒比海盗》里的说法。Agent 把这些规则当成海盗准则,也就是”参考意见”,不一定执行。
长提示词还会遇到 Lost in the Middle。随着上下文不断增长,开头和结尾的内容更容易获得模型注意,中间的信息则容易被忽略。即使规则最初写在前面,只要对话继续变长,它们也会被挤进上下文中部。
Lost in the Middle
当上下文很长时,模型更容易使用靠近开头和结尾的信息,对中间内容的利用能力会下降。写在长提示词中部的规则因此更容易失效。
Uncle Bob 因此把两类信息分开处理。
必须让 Agent 一开始就知道的内容,尽量缩短,放进初始提示。能够用程序判断的要求,则变成测试、复杂度检查、变异测试和依赖规则。Agent 可以忘掉提示词里的第 80 条规定,却绕不过一个失败的测试。
这些工具会让 Agent 变慢。它要反复修改代码,直到所有检查通过。Uncle Bob 承认,这里存在一个平衡点。如果检查多到让 Agent 比人还慢,这套方式也就失去意义。
他尚未找到那个边界。以目前的实验看,即使加入这些检查,Agent 的速度仍能达到人的两到四倍。
这个思路比继续研究”完美提示词”更接近工程。提示词给出方向,工具决定是否合格。前者依赖模型记住和理解,后者返回通过或失败。
03
多 Agent 首先解决的是上下文污染
Matt 对多 Agent 团队一直有些怀疑。
社交媒体上常有人宣称,自己已经解雇整个团队,改用上百个拥有不同职位、还会互相发邮件的 Agent。Matt 认为,大多数开发任务也许只需要两个角色。一个负责尽快实现,另一个负责审查和重构。
Uncle Bob 认为,多 Agent 有两个实际价值。
第一个价值是并行。几名编码 Agent 可以同时工作,他的笔记本能运行的远不止三个。
第二个价值是控制上下文。每个 Agent 只负责一种任务,完成后立即结束。下一个 Agent 从干净的上下文开始,不会继承前一个 Agent 的思路和偏见。
他目前使用的流水线包含五个角色:
- Specifier 把人写的需求转成 Gherkin 验收测试和 QA 操作流程。
- Coder 编写单元测试、业务代码,并让验收测试通过。
- Cleaner 运行 CRAP 分析和代码审查,清理实现阶段留下的问题。
- Hardener 执行变异测试,检查测试是否真的能发现错误。
- QA 把人工测试步骤转成可执行脚本,从界面操作系统并检查结果。
Gherkin
一种用接近自然语言的方式描述软件行为的格式,常见结构是 Given、When、Then,也就是给定条件、发生动作、预期结果。它让业务要求可以进一步转成验收测试。
这套流程有明显开销。每个 Agent 启动都要重新理解项目,角色之间也需要传递信息。
Uncle Bob 举了一个自己的实验。一项任务交给单个 Agent,可能 5 分钟就能完成,但结果不太可靠。走完整条流水线大约需要 1 小时。同样的工作如果由人完成,可能要半天。他据此估算,流水线仍有四到五倍的速度优势,而且会执行人通常舍不得投入时间完成的质量检查。
这只是他的项目经验,不是一项可以直接套用到所有团队的性能测试。任务能否清楚拆分,验收标准能否自动执行,都会改变结果。没有这些条件,多 Agent 只会增加沟通成本。
04
架构还不能放心交给 Agent
自动测试可以检查行为,却不能自动给出好的模块边界。
在最近一个月之前,Uncle Bob 仍然亲自处理这部分工作。他会询问 Agent,系统有哪些模块,模块之间如何通信。得到的答案经常让他害怕。于是他重新设计模块结构,再让 Agent 按新的方案修改。
为了减少直接阅读代码,他让 Agent 做了一个架构查看器。界面会用类似 UML 的方式显示系统模块和依赖关系。他可以点击模块查看子模块,再继续深入到具体代码。
他还建立了一套依赖检查工具。人先在配置文件中声明哪些模块可以互相依赖,依赖应该朝哪个方向流动。Agent 如果违反规则,就必须通过倒置依赖、加入接口或拆分模块来修复。
这些工具可以维持已经确定的架构,却还不能可靠地设计架构。Uncle Bob 正在尝试自动化这一步,但进展并不顺利。他认为,人可能始终要参与阶段性的整理和重新划分。
Matt 在这里引入了 John Ousterhout 的深模块概念。一个浅模块暴露很宽的接口,内部却没有隐藏多少东西。深模块用较小的接口隐藏更多实现细节。
深模块
深模块用较小、稳定的接口封装较多实现细节。调用者只需要理解接口,不必同时理解内部所有代码。浅模块则暴露很多操作,却没有替调用者隐藏多少复杂度。
这种设计同样适合 Agent。模型可以先阅读接口,理解模块提供什么能力,不必立刻加载全部实现。测试也会帮助它理解模块应该怎样工作。
模块边界混乱时,Agent 会像正在讨论咖啡却突然听到肥皂剧的人一样分心。上下文里出现太多无关内容,它便难以判断当前任务属于哪条思路。好的模块化设计限制了每次需要理解的信息,也减少了修改向外扩散的范围。
AI 没有降低理解复杂系统的难度。它只是让糟糕的结构能以更快的速度扩散。
05
工程原则要保留,人的习惯不必照搬
Matt 问 Uncle Bob,《Clean Code》里的哪些建议需要为 Agent 调整。
Uncle Bob 的答案是,价值可以保留,执行阈值和操作习惯需要重新测试。
以函数复杂度为例,他过去给人的 CRAP 分数上限是 4。面对 Agent,他已经放宽到 6,还在考虑是否可以提高到 8。Agent 有更大、更准确的短期记忆,也许能够处理更多分支。这个上限究竟在哪里,他还没有答案。
测试驱动开发也需要调整。
Uncle Bob 是 TDD 的长期支持者。对人来说,先写一点测试,再写一点实现,可以减轻短期记忆压力,让开发者始终围绕一个小目标工作。
TDD
TDD 是 Test-Driven Development,也就是测试驱动开发。典型循环是先写一个会失败的测试,再写最少的实现让它通过,最后在测试保护下重构代码。这三个阶段通常称为 Red、Green、Refactor。
他不会强迫 Agent 模仿这个节奏。Agent 经常先写完一个函数,再为函数补测试。即使提示词要求严格执行 TDD,它最后也会回到这种方式。Uncle Bob 认为,这可能更符合 Agent 的能力特点。
他的区分很准确。我们应该把人的工程价值施加给 Agent,例如可测试、低复杂度和清楚的边界。没有必要强迫 Agent 复制人形成这些价值的每一个动作。
06
Agent 喜欢写计划,漂亮计划却很容易失败
如果完整的 Agent 流水线运行一次需要一小时,输入一个错误需求会造成很大浪费。Matt 因此追问,在开始执行具体工作之前,到底应该做多少规划?
Uncle Bob 尝试过详细的前期规划,结果并不好。
Agent 很喜欢写计划。它们会不断补充细节,把文档写得完整又漂亮。但一旦进入实现,人很快会发现计划遗漏了真实开发中的细节。Agent 没有足够的判断力处理这些未知情况,只能沿着错误方向继续执行。人最终还是要叫停、修改计划,再重新开始。
这种做法让 Uncle Bob 想起 20 世纪 70 年代的瀑布开发。先把一切定义清楚,再一次性交给开发阶段。几十年前,人做不到。换成 Agent,也没有消除需求和实现之间的未知情况。
他正在重新尝试敏捷开发。先完成一两个用户故事,检查行为和架构,必要时由人重新整理。然后继续下一小步。
他用了一个房子的例子。假设建房子的每次修改都只花 1 美元,包括重新打地基、移动楼梯和更换房间位置。你还会先花几千美元请建筑师画出一份不能更改的完美方案吗?更合理的做法,是先搭出一部分,让家人走一遍,发现动线不好就再改。
Agent 已经把代码修改成本压得很低。前期计划并没有同步变得更准确。小步实现、快速反馈和持续整理,因此重新变得划算。
Uncle Bob 也不把规格文档当作需要永久维护的源码替代品。规格可以临时存在,随着理解变化而消失。对于他公开的 CRAP 工具、变异测试工具和 Agent 流程,他甚至建议别人不要直接下载。更好的办法是让 Agent 阅读这些实现,再针对自己的项目做一个版本。
运行中的程序和自动化检查,比一份长期无人阅读的规格文档更接近真实约束。
07
新人要先学会做 Agent,才可能指挥 Agent
访谈最后转向一个难题。
Matt 用 John Ousterhout 的说法区分了战术编程和战略编程。战术工作负责把具体功能做出来,战略工作负责模块划分、技术方向和长期结构。Agent 擅长前者,却不擅长后者。
战术编程与战略编程
战术编程关注眼前功能怎样实现。战略编程关注系统长期如何演化,包括模块边界、依赖方向和技术选择。Agent 已经很擅长执行局部任务,后者仍需要人承担主要判断。
如果初级程序员过去通过战术工作积累经验,而这些任务现在大量交给 Agent,他们该怎样成长为能做战略判断的人?
Uncle Bob 没有假装自己已经有答案。他给出了一个仍很粗糙的培养路径。
学习者首先要亲自写代码。也许写一年,也许需要别的时长。重点是亲手经历实现过程,知道 Agent 正在面对什么。
进入大量使用 Agent 的公司后,新人可以先被当作一个 Agent。负责人把同样的实现任务分给新人,让他接受测试、复杂度检查和变异测试的约束。这个阶段可能持续几个月,产出会很低,但新人能在密集反馈中看到自己的代码为什么失败。
之后,他才开始监督和运行自己的 Agent。
Uncle Bob 仍建议新人理解计算机底层。十年前,他会让只写 Java 的程序员花一个周末学习汇编语言,因为不了解下面发生了什么,就容易把抽象层当成魔法。现在,这条学习路径仍然成立。学习者应该理解二进制、汇编语言、C 一类的底层语言、高级语言,再进入 Agent 和自动化工具。
他还建议阅读一些老书,例如 Tom DeMarco、Ed Yourdon 的作品和《程序员修炼之道》。其中一些技术细节已经过时,但软件复杂度、模块化和工程判断的经验正是在那个年代形成的。
真正难教的,是如何识别 Agent 正在挣扎。
Uncle Bob 之所以能看出 Agent 陷入循环,是因为他自己经历过同样的困境。新人看到 Agent 反复修改时,可能只觉得它还在工作。资深程序员却能认出,这是代码结构已经开始阻碍修改。
AI 也许能缩短这类经验的反馈周期。Matt 举例说,过去一个架构错误可能在九个月后才暴露。Agent 快速推进项目,也会更早撞上那堵墙。新人需要看到失败过程,只拿最终生成的代码学不到这些判断。
08
程序员的工作会离代码远一点,离工程更近一点
Uncle Bob 的目标,是尽量不再逐行阅读 Agent 生成的代码。他仍会查看 CRAP 分数、抽查实现,并运行其他测试,但不想用人的速度限制 Agent。
程序员仍然要参与开发,只是更多工作移到了代码周围。定义模块边界,选择质量阈值,把模糊要求变成可执行检查,在小步迭代中判断方向,这些任务决定了 Agent 最终会加速什么。
每次编程抽象层提高,都会有人担心程序员失业。机器码之上有汇编语言,汇编语言之上有高级语言和编译器,现在又多了模型。
工具替人完成了更多操作,但没有替人消除软件本身的复杂度。
Uncle Bob 认为,软件工程基本原则仍然有用,因为它们帮助人和模型组织复杂度。那些现在被嫌弃为过时、准备扔掉的规则,很可能在 Agent 把项目推到失控边缘之后,又被人重新捡回来。
09
结语
两人的讨论反复指向代码库中的复杂度。
模型可以不断升级,代码库仍然会积累问题。Agent 能加快实现,也能加快混乱。工程师要决定哪些结果可以接受,用测试和工具留下可以重复执行的标准。
这也是我喜欢这场直播连线的一点,真实,很多问题并没有确定性的答案,但他们的一些思考和实践确实带来很多启发。
希望对你也有用!
[全文完]
如果你对 AI 时代如何创作与学习 感兴趣
请添加下方微信,一起学习交流