背景介绍与传奇程序员
大飞: 大家好,这里是最佳拍档,我是大飞。
“我完全不看Agent写出来的任何代码。”
这句话如果是从一个刚毕业的程序员嘴里说出来的,我们可能会觉得他不太靠谱。但是说这话的人是罗伯特·马丁(Robert C. Martin),全球开发者更熟悉的称呼是Bob大叔。他编程超过五十年,写了《代码整洁之道》(Clean Code),是软件工程领域被引用次数最多的作者之一。一个把代码质量看得比什么都重的人,现在说他不看AI写的代码。
今天我们就来聊聊这件事儿。Bob大叔最近和知名TypeScript教育家马特·波科克(Matt Pocock)做了一场直播对谈,聊的就是AI时代的软件基础,以及他这几个月和AI Agent一起写代码的真实经历。整场对谈不光是聊到了AI怎么写代码,更核心的问题是:当代码的生成速度被大幅提升之后,程序员到底应该把时间花在哪里?
先来给大家介绍一下Bob大叔这个人。他1964年就写了第一个程序,当时才十二岁。那台机器是他母亲送他的生日礼物,一种小型的模拟电脑。当时得把白色的小管子套在插桩上来编程。在他看来,那本质上是一个3位的有限状态机,但是对十二岁的他来说已经足够着迷了。后来他父亲给了他几本编程语言的书,包括Fortran、Cobol、PL/I,他全读完了。只是当时没有机器可以运行程序,他只能把程序写在纸上,在脑子里运行一遍。十六岁的时候,他找到了一份可以写代码的临时工作;十八岁找到第一份正式的程序员工作,从那以后一直做到今天。
这场对谈还有一个有意思的开场:Bob大叔是穿着浴袍出来的。这其实是他个人形象的一部分,源于他所谓的“晨间浴袍吐槽”。大约两年前,他早上六点穿着浴袍坐在前廊,开始琢磨SQL注入这件事,越想越觉得不对劲,掏出手机发了一通牢骚,没想到反响不错。后来就陆续做了几次,用这种半开玩笑的方式来表达对技术问题的看法。
从给Agent擦屁股到确定性工具
大飞: 接下来我们进入正题。去年圣诞节前后,Bob大叔开始认真尝试AI编程工具。最早试的是ChatGPT、Grok这些,一开始其实没觉得有多惊艳。后来他找了一个Agent让它帮忙写代码,写得不怎么样,但至少确实写出来了。当时他正忙着一个项目,就让Agent参与了工作。不过那个阶段他一直在给Agent擦屁股,因为它经常把事情搞得一团糟。速度确实快,但总会留下隐患。他的感受很矛盾:AI挺有意思,因为确实快;但是也很让人沮丧,因为反而让自己的效率变慢了。
转折发生在他意识到一件事:既然Agent这么快,那它其实可以做一些他自己根本做不到的事情。这里他提到了两个很早就有、但是一直没法落地的想法:
一个叫CRAP,是一个缩写,把代码测试覆盖率和每个函数的圈复杂度结合起来,通过一个公式算出一个分数,衡量一个函数到底有多糟糕。2000年代初他就跑过一次,确实找出了一堆糟糕的函数,但只能一个一个地修,还得重新编写测试,成本太高,只能搁置。
另一个叫变异测试(Mutation Testing),原理是让一个小程序自动修改你的源代码,比如把负号改正号、小于号改大于号,每改一次就跑一遍完整的测试套件。预期测试应该失败,因为代码被故意改坏了;如果测试没失败,就说明出现了一个存活的变异体,需要处理。这个方法他也试过,当时得跑一整晚,虽然是找出不少问题,但同样没法纳入正常流程。
到了今年初,他突然想到:AI速度快,而且根本不在乎工作有多枯燥,那干脆让Agent去跑CRAP,再让它负责重构和清理代码;也让它跑了跑变异测试。以前要跑一整晚的任务,现在三十分钟就能完成了,而且还能把所有测试漏洞补上。他意识到,AI写代码时虽然会留下很多碎屑和浮毛,但是它们自己也许就是清理这些东西最好的工具。
于是他不断尝试,加入更多工具,让多个Agent配合工作。到现在为止,效果相当不错。他现在的原则很明确:让Agent去干活,运行这些工具。他正在努力达到一种状态——不需要亲自去看代码,也可以信任AI。当然他还是会通过其他方式来验证代码的质量,比如检查CRAP分数、抽查代码、运行其他测试等等。但是总体思路是:既然Agent处理代码的速度比人类快得多,而人类处理代码很慢,那就让Agent负责写代码,人类来处理更高层面的事情,确保一切正常运转。
规则不是建议:确定性检查 vs 提示词
大飞: 不过这里有个关键问题绕不开:AI写的烂代码该怎么办?
Bob大叔很早就发现,如果一直让Agent往下做,不去清理它留下的烂摊子,它的速度反而会越来越慢。比如改了一个地方,无意中破坏了另一个地方;为了修复那个地方,又破坏了其他地方,最后不停绕圈子。这些Agent虽然快,也确实聪明,但是和人类一样,也会受到烂代码的影响。代码烂到一定程度,Agent就处理不了了,开始原地打转,然后把烂摊子越搞越大。他甚至遇到过一个Agent直接跟他说:“我处理不了了。”
那怎么解决呢?大多数人可能会选择给Agent增加指令,在配置文件里堆满规则,每看到坏代码就加一条。但是Bob大叔选了另一条路:确定性的自动化检查机制。
他解释了为什么不选择引导的方式。最开始他确实这么做过,给Agent写的提示词都是“这是测试驱动开发的方法”、“这是整洁代码的要求”之类的,最后会得到一份长达十页的文档专门告诉它什么是好代码。但是他很快发现,这些模型对待规则的态度特别像《加勒比海盗》里的海盗法典一样——听起来是规则,但是对它们来说更像是一堆建议。
背后其实是有技术原因的。他研究之后发现,这和一个叫Lost in the Middle的现象有关。模型的上下文窗口越来越大,放在最前面和最后面的内容更容易被关注,而夹在中间的内容更容易被忽略,甚至像消失了一样。比如你写了一份很长的提示词,开头前三句它记得很清楚,当作高优先级指令;但是到了第五十句、第八十句,那些内容可能就被丢到上下文中间的某个角落了。
但是确定性的工具不会这样,不会因为规则写在第五十行还是第八十行就把规则当建议。所以他认为使用Agent的关键,就是把初始提示词精简到绝对最小值,尽可能多地保留在优先级区域,然后在这之后使用确定性工具,虽然这确实很难做到。
马特补充了一个很好的说法,叫上下文窗口的聪明区和愚蠢区,这是戴克斯·霍瓦斯(Dex Horvath)提出来的概念。随着上下文不断变长,Transformer中的注意力机制会越来越吃力,信息逐渐被稀释。就像一个越来越拥挤的房间,每个Token都在大声说话,最后你很难从一片噪音中分辨出真正重要的信号。
Bob大叔还提到,自动化检查确实存在一个过多的临界点。如果它们让Agent的速度慢到还不如人类,那你就输了;但是只要生产力仍然高于人类,你就依然领先。根据他目前的观察,大概能把生产力优势维持在两到四倍左右。使用确定性工具会明显拖慢Agent,因为你实际是把它放进了一个循环里,不断修改代码,直到工具告诉它OK为止。Agent就在那里不停循环,修改这个、修改那个,增加测试、降低圈复杂度、拆分函数,需要很长时间才能达到预设的合规标准。本质上是在牺牲一部分生产力,来换取更高的代码质量。
多Agent协同流水线
大飞: 他正在尝试让多个Agent彼此协作、相互交接。比如一个负责写代码,下一个负责审查,再下一个负责测试和强化。虽然有巨大的通信开销,但是整体速度仍然比人类快得多。
说到多Agent协同,这其实是解决上下文失效问题的一个关键思路。Bob大叔解释了两个优势:第一可以并行运行多个Agent;第二当任务聚焦到单一任务时,可以更好地控制上下文窗口,Lost in the Middle的问题会减轻。你甚至可以设置这样一套机制:让Agent出生、完成任务,然后消失;下一个Agent进来时,面对的是干净的上下文窗口。缺点是启动时间更长,一个Agent可能需要十到十五秒才能启动并且理解当前上下文。
他倾向于尽可能把任务拆得足够细、足够聚焦。具体来说他有一套完整的流程:
- 先运行一个规格定义器,把人类编写的文档转换成Gherkin语言和QA流程;
- 然后交给编码器,负责编写单元测试、实现故事逻辑,让Gherkin测试跑通;
- 完成后交给清理器做复杂度分析和常规代码审查;
- 接着交给强化器运行变异测试,反复验证测试是否真的有效,确保百分之百的测试覆盖率(这个过程非常耗时);
- 最后交给QA Agent,把书面QA文档转换成可执行脚本,用脚本直接操作系统,给出确定性的测试结果。
如果能通过这一整套流程,最终得到的程序质量会非常高。他自己的实践很成功:给单个Agent一个任务可能五分钟就完成了,但是结果是否可靠很难说;而采用这套流程可能需要一个小时,但是依然划算,因为让人类完成同样的工作可能需要半天。
架构设计与深层模块
大飞: 在架构设计方面,Bob大叔也有不少的思考。过去一个多月前,他一直是手动完成模块结构设计的:先让Agent构建东西,然后通过不断提问来审问它们,比如这里的结构是什么?模块和模块怎么关联?等他得到那些令人恐惧的答案后,就亲自设计模块结构,告诉Agent该怎么划分、怎么通信。这个过程非常辛苦,所以他让Agent帮他构建了一个架构查看器,可以在屏幕上弹出类似UML的图表,展示整个系统的模块结构和依赖关系,可以点击查看子模块甚至直接看代码。
他还构建了另一个确定性工具,可以定义哪些模块应该依赖哪些模块、哪些绝对不能互相依赖,形成一份Agent无法违反的规范文件。如果违反了就必须通过反转依赖、提取接口等方式修复。
马特提到了约翰·欧斯特豪特(John Ousterhout)的深层模块(Deep Modules)概念,即接口简单但是内部隐藏大量信息的模块,对Agent来说非常理想,因为它们只需要读取接口而不必理解实现。Bob大叔非常认同这一点,指出模型非常关注接口的名称和结构,这意味着它们不必阅读下层代码。当然这既是一种风险,也是一种优势。他还在《代码整洁之道》附录里记录了和欧斯特豪特之间的一场长篇辩论,他自己玩得很开心。
传统工程规范的调整与重塑
大飞: 接下来聊到了传统编程规范在AI时代是否需要调整。Bob大叔提到了几个具体的调整:
首先是阈值问题。Agent能处理的复杂度和人类开发者是不一样的,它们的短期记忆比人类强得多,而且更加精准。所以他把CRAP分值从人类的4以下放宽到了6,甚至在考虑提高到8。在测试覆盖率达到百分之百的情况下,CRAP分值为6意味着这个函数有六条执行路径,而且每一条都经过测试。他还和Agent就这个问题争论过多次,Agent似乎也认为6是个不错的标准。
另一个重要的调整是关于测试驱动开发(TDD)。他曾经是TDD的坚定拥护者,但那是一种人类的纪律,是根据人类的思维方式演化出来的。他不会也不打算把这种纪律强加给Agent,强迫Agent写一行测试再写一行生产代码没有什么意义。虽然对人类来说这样做收益很大,但是对于Agent来说,他更愿意允许它们采用欧斯特豪特提倡的方式:先写函数再写测试。事实上即使他要求Agent严格按照TDD来做,它们最终也总会回到先写代码、再写测试的模式。所以他的结论是:把人类的纪律强加给Agent可能是一个错误。当然,虽然我们不需要强加纪律,但是仍然需要坚持人类的价值观,只是阈值需要根据Agent的特点调整。
敏捷开发 vs 前期过度规划
大飞: 关于前期规划,Bob大叔有一个很明确的判断:现在最大的诱惑是过度编写规格说明,让开发者不断完善需求和计划,再把这些东西一次性交给Agent。
这是一个源自七十年代的古老诱惑,后来瀑布流开发模式就是这种思路的典型;而敏捷开发的兴起,很大程度上就是对这种做法的反思。过于沉重的前期规划往往会把事情搞得一团糟,因为最终做出来的东西几乎不可能和最初设想的一模一样。面对Agent,人们同样容易掉进这个陷阱。他这周就试过这种方式,结果一次次证明是灾难。Agent根本无法完全按照你制定的计划执行,因为你不可能提前考虑到所有的细节,Agent也没有你那么强的判断能力,很容易朝错误方向一路跑偏,你只能叫停、回滚、重新计划、从头开始。
所以他放弃了这种做法,回到敏捷的方式:先让Agent完成一两个具体任务,看看整体架构有没有问题,如果需要就手动介入做一些调整,再让它继续。他怀疑我们可能永远无法完全摆脱最后这步人工组织的工作。
对于现在流行的规格驱动开发(Spec-Driven Development),他的直觉是这条路行不通。Agent非常喜欢写计划,会不断修饰,写得越来越华丽完美、细节越来越丰富,但是到了真正执行的阶段,往往还是会崩盘。
他用了一个很妙的比喻:假设改建一栋房子的成本只需要一美元,包括打地基、修屋顶以及之后所有的修改,每次都只需要一美元。你会先花几千美元请建筑师设计一套完美方案,再花一美元一次性把房子盖出来呢?还是直接走到承包商面前说“地基打在这里,做成这个形状;噢,不对,改一下,厨房放这边客厅放那边;等等,还是换个位置吧”?显然后一种更合理。现在软件修改的成本已经大幅下降,接近于零,所以为什么还要花大量精力做昂贵的前期规划呢?规格说明是转瞬即逝的,会消失也会频繁变化,不等同于源代码。
对新生代开发者的忠告与软件基础
大飞: 最后是对新生代开发者的忠告,也是这场对谈里最值得深思的部分。
马特提到了欧斯特豪特关于战术编程和战略编程的区分:战术编程像地面作战的士官,负责具体战斗;战略编程像将军,从更高层面指挥整场战争。问题是Agent非常擅长战术却非常不擅长战略。那么刚入行的人,既然AI已经吞掉了大量战术性工作,该如何学习战略编程呢?
Bob大叔坦言他没有完美的答案,但是分享了他的想法:
首先,程序员学习编程的方式都应该是写代码,你应该先写上至少一年代码,这样才能真正知道Agent究竟在处理什么。
下一步,当你入职一家大量使用Agent的公司时,作为一个刚完成培训的年轻人,你应该被当作一个Agent来对待。那位手下运行着一堆Agent、自己负责战略决策的首席工程师,应该把你视作一个Agent,给你分配和Agent一样的任务,让你接受和Agent一样的确定性工具约束。你应该在这种状态下待上几个月,虽然产出很低,但是能学到非常多东西。等你通过了这种严酷考验,也许你才会被信任去亲自运行一个Agent。
他还强调,你不能完全丢掉代码。十年前他常告诉人们,如果你从来没有写过汇编语言,就应该花一个周末写写汇编,这样才能知道后台到底发生了什么。如果你整天只写Java,那你就是生活在一个幻觉世界里,那里仍然存在很多你不理解的魔法。这一点在今天依然成立。在这条学习路上,你必须从最基础的东西(比如二进制)开始,一路经过汇编语言、像C这样的基础编程语言、像Python这样的高级语言,然后进入处理Agent级别的工作,学习使用确定性工具,最后才能在监督下真正开始战略性地运行Agent。
他还推荐去读那些老书,比如汤姆·德马科(Tom DeMarco)、埃德·约尔顿(Ed Yourdon)的著作,以及《程序员修炼之道》(The Pragmatic Programmer)。这些书写于七八十年代,很多重要的经验和教训恰恰是在那个时候总结出来的。你需要过滤掉一些过时的内容,但很多核心的东西依然适用。
至于怎么判断Agent在犯错,Bob大叔说,早期他就是看着代码,发现里面的不好代码,但那其实不是最重要的部分;更重要的是他会看着Agent瞎忙,他能看出Agent什么时候在挣扎,因为作为一名程序员,他自己也经历过同样的挣扎。问题在于,一个刚入行的人可能根本识别不出这种挣扎,他是通过艰苦的实践学会这一点的。
最后回到“软件基础”这个核心话题。Bob大叔引用了戴克斯特拉(Dijkstra)的话:软件是人类迄今为止尝试过的最复杂的东西,比我们做过的任何其他事都要复杂。基础是我们组织复杂性的一种方式,让复杂的东西变得可以理解——不仅是让人类理解,也让模型理解,毕竟模型终究也是模仿人类建立起来的。
那些认为软件基础已经不重要的人,会吃到苦头,而且不会等太久。虽然可能比他想象的更久一点,因为Agent确实很厉害,但是他已经见过它们撞墙了,他知道那堵墙就在那里。每一次抽象层向上提升,从二进制到汇编,从汇编到编译器,从编译器到模型,处在更低层级的人都会抱怨说这会毁了一切、我们都没工作了、编程变得这么简单、五岁小孩都能写代码了……每一次都是同样的故事。但同样的规则依然适用,你今天扔掉的那些规则,一年之后很可能还是会从地上把它们捡起来,掸掉灰尘,重新想起为什么当初需要它们。
Bob大叔的这套实践和思考,本质上不是在说AI不行,也不是在说传统方法是万能的。他在说的是:当代码的生成速度被大幅提升之后,组织复杂性的能力、判断代码质量的能力、识别Agent何时开始挣扎的能力,这些更高层面的东西非但没有过时,反而变得更加重要了。那些以为有了AI就可以跳过基础的人,迟早会撞上那堵墙。
如果你对AI编程有什么想法,或者自己在实践中遇到什么困惑,欢迎在评论区聊聊。感谢收看,我们下期再见。
📌 文中提及的人物和组织
人物: Robert C. Martin
媒体/书籍: Clean Code