约四周前,这一观点从编程大师Robert C. Martin的口中提出,立刻在技术圈激起了不少讨论声。
Robert C. Martin——即享誉全球的”Uncle Bob”(叔叔鲍勃),《代码整洁之道》的撰写者——已在编程领域耕耘逾50年。步入AI时代后,他做了一个在不少码农看来颇为出人意料的尝试:将代码编写任务交由Agent承担,而自己则完全放弃了传统的逐行人工审查流程。

对长期依赖Code Review流程的工程师们而言,这套方案明显难以接受。许多人由此提出质疑:如果AI生成的代码无需人类逐一阅读检验,开发者又该如何确保其正确性?
显然,叔叔并非采取”不加甄别”的态度来接纳AI创作的代码。他为Agent建立了多重约束机制:单元测试、Gherkin规范测试、QA流程、质量指标评估、变异测试技术、覆盖率检测,以及数量庞大的自动化审查环节。唯有当代码通过了这些层层递进的测试与质量评审,他才会对最终产物产生”充分的信任”。
他采用的这套方法具体包括哪些环节?在整个过程中有哪些值得注意的坑点?
近期,叔叔同知名TypeScript教育家Matt Pocock展开了一场在线对话,深入讨论了AI时代的软件工程基础,以及过去几个月内他与Agent协作编程的实际案例。

在他的观点中,关键不在于抛弃软件工程学科,而在于调整人与代码的协作分工:与其依赖人工逐行检查Agent的产出,不如构建一套有确定性保障的规则与工具体系,让它们自动判断代码是否合规。
当涉及如何识别Agent生成的”劣质”代码时,叔叔坦言他能察觉到Agent何时陷入困境,因为作为程序员的他曾多次经历这种状况。但问题所在——一位初出茅庐的程序员根本无法辨识出这种困顿迹象。
这由此引发了他对AI时代软件从业者的另一项提醒:编程的基础知识并未淘汰。”有人如今觉得编程基础已无关紧要。他们马上就会为此付出代价,而且不需要等太久。”
当Agent处理代码的速率远超人类手工能力时,程序员应该将精力转向何处?
这场对话也许能够提供一些值得思考的启示。
完整采访详见:https://www.youtube.com/watch?v=zcLPGC-tvgk

超过六十年的编程之路,Bob叔叔那个著名的”浴袍段子”
Matt Pocock:各位下午好。我为今天的直播准备了一份意外惊喜。我邀请到了一位我长期想深入交流的人物,他在软件领域的影响范围不仅跨越我的整个职业生涯,实际上比我从业以来的岁月还要久远。目前这位人物也开始在AI Agent领域大展身手。
我们今天特别邀请到了Uncle Bob(Robert C. Martin),他穿着浴袍,已做好准备开始对谈。Uncle Bob,欢迎你的到来。
Uncle Bob:很荣幸能够参与这次对话。
Matt Pocock:邀请到你真的很让我高兴。对于那些不太了解你”浴袍”这个梗的观众朋友,我想我们应该为其做一番解读。这个梗是怎么来的?为什么浴袍成了你个人风格的象征?
Uncle Bob:这件趣事发生于大约两年以前。那时是清晨六点光景,我穿着浴袍坐在前廊上。
当时我的想法集中于SQL注入这一安全隐患是多么严重的问题。让一门文本型语言直接扮演数据库访问的角色,从安全的角度看实在是设计得很不合理。
我当时身着浴袍,思考得越来越觉得不妥当,最后就掏出手机发表了一通吐槽。这就是之后人们所说的”浴袍清晨吐槽”的起源。没料到反馈还算不错,后来我又陆续做了若干次这样的事情。
Matt Pocock:那你今天过来也是抱着这样的心态吗?
Uncle Bob:我这个人脾气向来有点儿古怪。此时还是大清早,我连早上的咖啡都尚未饮用,所以我劝你最好别惹我。
不过现在情况已经不同了。其实现在已经是上午10点,浴袍也脱掉了,咖啡也喝过了,刚才还把一罐低卡可乐给喝完了。现在我的精神状态相当不错。
Matt Pocock:很好。既然你现在穿上了Polo衫,那你的个人经历又是怎样的呢?我这儿的观众群体以开发人员为主,不过也有一些非技术背景的人员,我们该用什么样的方式来介绍Uncle Bob这样一位标志性的人物呢?
Uncle Bob:说我是”标志性人物”未免过誉。我就是一个从事编程的人。从事这个工作已经持续了很长时间,超过五十个年头了。
我在1964年写下了我最早的一个程序,那时我只有十二岁。这段程序在我妈妈为我十二岁生日购买的一个小型模拟计算装置上运行,编程需要通过把一些白色小管件插入相应的插孔。本质上讲这是个只有三位的有限状态器,但对当时的十二岁的我来说,它简直魅力无穷。
Matt Pocock:那你是如何从十二岁走到今天这样一个状态的呢?
Uncle Bob:其实时间早就超过五十年了。从那以后,我开始尽可能地去吸收编程方面的知识。我父亲给我买了一本有关Fortran的著作、一本Cobol的著作,还有一本PL/I的著作,这些我都读完了。只不过那时候根本没有可用的机器来执行程序,我只能把代码写在纸上,然后在脑子里把它”执行”一遍。
十六岁的时候,我找到了一份可以敲代码的工作,尽管那只是一份临时性的岗位。到了十八岁,我拿到了一份真正的程序员工作,从那时候开始到现在,我一直在做程序员。
Matt Pocock:所以你已经在这个”战壕”里呆了相当长的时间,还为这个领域贡献了一些很有分量的作品。
Uncle Bob:确实是这样。我出过几本书,其中一部作品传播得相当广泛。《代码整洁之道》(Clean Code),这是第二版。

“我无需亲眼查看Agent写的代码,也能对其建立充分信任”
Matt Pocock:只要我和人谈起一些重要著作,在软件工程范畴内,这本书总是被引述最频繁的。其影响力确实是无比深远的。这正是我把你请来的原因,因为你在前AI阶段就已经拥有如此广泛的影响。
那么Uncle Bob,而今AI已经成为现实,你的想法发生了什么样的转变?
Uncle Bob:大约在去年12月左右,这件事确实让我大吃一惊。
假期期间,我开始尝试着运用ChatGPT、Grok一类的工具。初期我并没有觉得特别惊艳。但随后我逐渐领悟到,这些工具可能比我的认知更富潜力。
于是,我开始寻觅一种”智能体”(Agent)。我印象中最早接触的应该是那时还处在初期阶段的Grok。我让它协助我完成一些代码编写,实际效果不算太理想,但起码它真的把代码给写出来了。
当时我正投入到某个项目中,我想也许这东西确实能帮上忙。于是我开始让它参与到我的工作流程里。但在那个时期,我一直在做”擦屁股”的工作,因为它经常搞得一塌糊涂。它的速度是快,可总是会留下各种问题。
那时我的心情相当矛盾:它有意思,因为确实飞快;它又令人失望,因为它反倒让我的工作速度变慢了。
后来我开始思量,既然它这么迅速,那它或许可以完成一些我根本达不到的任务。
很久以前,差不多在本世纪初吧,我曾有过一些我自己认为很出众但当时完全不现实的念想。其中之一叫CRAP,这是个首字母缩写。它能将测试覆盖率和函数的圈复杂性结合在一起,再通过一个比较复杂的公式运算产生一个评分,用来评估某个函数的质量。
本世纪初期,我觉得这是个非常妙的创意。我曾在一个规模庞大的项目上试验过这个概念,确实找出了大量质量低下的函数。可惜的是,我只能够逐个地去改进这些函数,并且需要重新设计对应的测试,代价实在太过高昂。所以最后我只得暂时搁置这一想法。
另一项让我产生浓厚兴趣的创新叫做”变异测试”(Mutation Testing)。原理是利用一个小程序去自动调整你的源程序代码,比方说把减法改成加法、把小于号改为大于号、把相等号改为不相等号。每次的修改之后,它就会把整个测试集合再运行一次,而且测试本应失败才对,因为代码已被故意破坏了。如果测试竟然通过了,就表示出现了一个”存活的变异体”,这个问题需要被处理。
大概在两千年前后,我在那个项目上也尝试过变异测试。当时我需要让它跑上整个晚上,因为每一轮完整的测试套件需要耗时4分钟,而我要重复运行好几百次。这种方法确实指出了许多”存活的变异体”,但同样很不切实际,我根本无从把这个工具纳入日常的构建流程。
到了去年12月或本年1月左右,我突然想到:Agent的速度特别快,而且它们丝毫不在乎工作有多单调无趣。所以我让智能体去执行CRAP分析,再让它负责对代码进行重构与优化。看着它完成这种工作真的很有意思。
我还让它运行变异测试。以前或许要耗费整个夜晚的任务,如今三十分钟就搞定,而且它还能把所有测试缺陷补齐。
那个时候我就意识到,这也许是一种清理代码中那些”垃圾”与”碎末”的好法子。Agent在创作代码时会留下大量的”杂质”,但它们自己也许就是消除这类东西的最好工具。
所以我继续进行探索,源源不断地加入更多工具,让这些智能体各司其职地一起工作。时至今日,它们的表现已经相当令人满意了。
因此我现在遵循的基本原则是:把任务分配给智能体去完成,让它们运用这些工具。我在努力达到一个这样的目标——我甚至根本不必亲自查看代码内容,也能完全信任它们。
当然,我仍旧会用其他的手段去验证代码质量,比如查看CRAP分值、做抽样的代码查阅、执行其他的测试项目。但总的来讲,这就是我现在努力追求的状态。
因为它们的代码处理能力远远胜过我的手工速度,而我处理代码又是那么缓慢,那就让它们去负责代码生成,我把更高层面的决策交给自己,确保全盘的进展能够顺利推进。从目前来看,成果相当理想。

Agent生成的”劣质代码”要怎样处理?
Matt Pocock:那你的目标就是让自己脱离具体的代码撰写和手工评审的环节,构筑起包绕代码的”约束框架”,给智能体套上”紧身衣”,让它无法越界。
但这里面有一个假设:你提到了Agent写出的代码会存在”问题”或”坏代码”。既然Agent这么高效,我们为什么要操心劣质代码?为什么不干脆顶着漏洞继续推进,直到这些漏洞被逐步消除?
Uncle Bob:我很早就察觉到了一个现象:假设我一直让Agent持续工作,却不去收拾它制造的”烂摊子”,它实际上会变得越来越缓慢。
它会陷入一种困顿:改掉一处地方,结果却意外地破坏了另一处;为了修复那一处,又无意中搞坏了其他的地方。最后就陷入了无穷无尽的循环。
我最终理解到,这些智能体尽管速度很快、也很聪慧,但和人类没什么两样,它们也会被低质量的代码所困扰。它们或许能够容许的程度与人类有所不同,但这个限度仍然是存在的。
代码要是变得太过混乱,Agent就没法继续处理下去了。它们会开始原地踏步,把原本的”麻烦”越搞越复杂。我甚至碰到过一个智能体直接告诉我:”我处理不了了。”
Matt Pocock:那你的解决办法是什么呢?怎样把这些”垃圾代码”清理掉?绝大多数人碰到这种情况会选择给Agent塞入更多的指令,比如在claude.md或agents.md里堆积一大堆规则。每当看到坏代码,就加一条新的指令。而你选择的是一套具有确定性的自动检查机制。为什么你没采用这种”引导”的方式?
Uncle Bob:一开始,我确实是这么做的。最初给智能体编写的提示词,大体上都是这一套逻辑:遵循测试驱动开发(TDD)的方法、遵守整洁代码的标准、你的代码应该达到什么样的水平、你应该执行哪些规则。
最后,你可能会得到一份长达十来页的文档,其目的就是告诉它什么样的代码才是”上乘的”。
但我很快就发现,这些模型对规则的态度,特别像《加勒比海盗》电影里的”海盗法典”——听上去是规则,不过对它们而言,更多像是一堆”建议”。
Matt Pocock:没错,这正是我脑子里想起的那个比喻。
Uncle Bob:它们真的会把规则给”软化”掉。这背后其实存在着技术成因。我后来专门钻研了一下这个模型的行为机制,发现这与一个名叫”中间遗漏”(Lost in the Middle)的现象有很大关系。
伴随模型的上下文范围越来越宽,处在最前面和最后面的内容通常会获得模型更多的关注,比夹在中间的内容更易被重视。比方说你写了一份冗长的提示词,最前面的前三句话它或许记得相当清楚,并把它们当作高优先指令;可等到了第五十句、第八十句的时候,那些信息就很可能被挪到上下文某处不起眼的角落。
面对海量上下文时,中间部分的信息更加容易被遗漏,甚至像是”消失”了。但确定性的工具则没有这样的问题。不论规则位处第五十行还是第八十行,它们都不会忽然把规则从约束变成”建议”。
我认为采纳智能体的关键——虽然这确实非常困难——就是把初始提示词浓缩到绝对最少,以便尽可能多地把内容留在注意力的”热区”,然后再之后利用确定性工具。
Matt Pocock:完全赞同。我把它叫做上下文窗口里的”聪慧区”和”蠢笨区”。这不是出自我的口,而是由Dex Horvath说过的。对我而言,这是一个非常妥帖的比喻。
在上下文的前半段,比如前十五万个Token,模型通常展现得相当智慧。然而伴随上下文变得更加冗长,Transformer中的注意力机制会越来越显得力不从心,各类信息也会一步步被稀释。
这就好比一个越发拥堵的房间,房间里的每个Token都在高声讲话,而且人数在源源不断地增多,最后你很难从嘈杂声中辨别出真正需要的信息。这种比喻对于我个人来讲非常有感触。
听起来,你在某种意义上是在舍弃”引导”(Steering)这个策略,改为重新拿起一些过去的自动化检查法子。到底是因为,这些检查工具不会像引导指令那样去占据上下文的空间,所以你能够不断地往上堆叠。
如果使用一个具有强类型系统和健全测试体系的编程语言,这套模式在你这儿看起来会如何呢?自动化的检查是否也存在”过多”的问题?
Uncle Bob:这正是我当前正在研究的课题。显然,自动化检查一定会有一个过量的临界值。最终,假设它们把Agent的速度拖到反而不如人工,那你就亏本了。然而只要Agent的生产力仍然优于人类,你就还处在上风。
按照我当下的观测,我可以把这类生产力优势保持在2到4倍上下。
自然,采用确定性工具会明显地降低Agent的速度,因为你实际上是把它放进了一个循环流程。眼下有许多人在讨论这类循环:你需要Agent不停地调整代码,直到工具最后宣布”OK”为止。
这样Agent就会不停地循环——更改这个、更改那个,补充更多测试,压低圈复杂度,进行函数拆分……它需要花相当多的时间,才能达到事先设定的质量要求。
所以本质上讲,你是在用一部分生产力去换取更优质的代码。
尽管眼下我还没找到这个方向的上限位置,但我正在尝试让多个智能体彼此配合、相互传递:一个负责代码撰写,下一个负责审查,另一个负责测试与增强。
这样做确实会引入巨大的沟通代价,可即便如此,它们加起来的速度还是比人类要快得多。

多个智能体的协同工作,破解上下文衰减这一顽疾
Matt Pocock:我们来深入聊聊多智能体架构(Multi-agent Systems),这个话题真的吸引了我。我一向对那种”我已经解雇了所有员工,现在手下有一百个Agent,每个Agent都扮演不同的角色,彼此互相交流,甚至有各自的电子邮箱”这样的言论存疑。
然而,对于把”执行”和”检查”分离这个思路,我乐意破例表示支持。好处不一定在于拥有两个高度专业的智能体,而是它有点像”红绿重构”(Red-Green-Refactor)的办法。
执行者只需要把测试跑通就算完成,不用把代码打磨得很漂亮。然后审查者就能接手。它不用重新从头理解整个问题,因为它已经拿到了执行者的变动情况,清楚地知道自己该审查什么。这时候,你就有机会给它塞入更多的引导指令。
我很想听你讲讲,关于执行者、审查者,还有你提及的”强化智能体”的想法。
Uncle Bob:运用多个Agent具备两项好处:
其一,你可以将多个Agent进行并行运作。比如同步执行三个写代码的Agent,我的笔记本电脑甚至还能支持更多数量。
其二,当你把Agent的职责聚焦于单一方向时,上下文窗口的控制效果会显著改进。”中间遗漏”这类难题会得到缓解,你还有余地在最前面积累更多规则,让Agent把工作做得更出众。
你甚至能创设这样一套机制:让Agent”应运而生、完成工作、然后消亡”。这样当下一个Agent进场时,面对的就是一个崭新的上下文窗口。缺点是启动的时长会更为拉长,一个Agent可能需要十到十五秒才能启动并吸收当前背景。
我的倾向是,要把工作拆分得足够细致、足够专注。
我会先运行一个”规格生成器”,它的工作就是把人类撰写的文档转换为Gherkin语言和QA流程。QA流程基本上是一套系统级检验,需要从最终用户的角度出发,在用户界面层面执行系统操作,验证系统可否正确地工作。
之后,这两份文档会交付给”代码编写员”。编写员的工作是撰写单元测试、实现功能逻辑,并使Gherkin测试能够顺利执行。
完成后,再送给”清洁工”。它的工作是进行复杂性分析和一般性代码评阅,把编写员遗留的”困扰”擦拭干净。
之后交递给”强化员”。它要执行变异测试。这个Agent特别无情,会通过对等号、小于号之类的东西做修改,反复验证测试的有效性,并确保达成百分百的覆盖率。这个流程会耗费很多时间。
最后交给”QA Agent”。它会把书面形式的QA文档翻译为可执行的脚本,用脚本直接跟系统打交道,给出确定性的检验答案。
若能够顺利通过这整个流程,最后获得的程序品质会相当出众。
这套方法我个人实验过,效果相当成功。给单独一个Agent分配一项工作,它可能五分钟就能搞定,可其结果是否值得信赖就存在很大疑问。而采用这套流程,可能需要耗时一个小时,但这仍旧是划算的,因为假使由人类去完成相同的工作,大约需要消耗半天。
Matt Pocock:没错,这其实是用前期的生产力投入,换取后期的收益,是对代码库的长期性投资。除了在上下文窗口的范围内做操纵,你同时还在塑造一个会话的”轨道”。要是你把智能体约束在同一窗口内继续做某件事,它就会沿着这个轨道往前走。
Uncle Bob:确实如此。这些模型内部存在一个人尽皆知的特性。即便你不属于程序员这一行业,比如你正欢快地与模型讨论怎样冲泡咖啡,忽然旁边路过了一个人,开始聊他正在收看的电视剧,而这个信息也进入了上下文窗口。打那一刻起,你再提及咖啡,模型就可能无缘无故地把咖啡和电视剧扯到一块儿。
模型很难真正区隔这两类信息。所以,你刚说的”轨道”这个概念特别恰当。只要能确保模型保持在正确的方向上,并尽可能地维持上下文窗口内容的一致,就能避免产生那些古怪的幻想,至少也能大幅降低偏离对齐的情况。
Matt Pocock:既然我们已经谈到自动化检查,那我想提一个问题:在你的前期筹备中,是如何对待代码库自身的架构构造的?优秀的测试集合当然很关键,可假如API的设计得很糟、或者模块的划分相当混乱,这会对这些自动化检查造成什么影响?
Uncle Bob:在大约一个月之前,我一直都是手工完成这个工作的。我会先让Agent建立一个东西,然后通过一连串的提问去”盘问”它们:这儿的框架如何?模块甲和模块乙怎样互联?模块最终是个什么概念?在我获取到那些令人警惕的答案后,我就会亲自去规划模块的框架,然后交代Agent:”模块应该这样去划分,它们之间应该以这样的方式通讯。”随即,再给它一份执行的方案。
这个流程相当艰苦,所以我让Agent给我开发了一个”架构检视工具”。它能在屏幕上弹出类似UML一般的示意图,展现整体系统的模块分布和相互依赖。我可以点开特定模块看一看里面的子模块,甚至可以直接查看对应的代码片段。这样的工具对我的帮助非常大。
除此之外,我还打造了另一个确定性工具,让我能定义哪些模块应该依赖于哪些模块,以及哪些模块绝对不可彼此依赖。这最终演变成一份Agent没法去违抗的约定。假如它们打破了这些约定,就必需通过反向依赖、提炼接口这些办法去进行纠正。
眼下我在尝试着把筹备这整个流程自动化,不过目前还没获得什么大的突破。
Matt Pocock:我也处于完全相同的局面。通过打造良好的模块设计,你能获得巨大的杠杆效应。你能否阐释一下这类杠杆效应体现在何处?
Uncle Bob:这和”差代码对比上佳代码”的争鸣是一脉相承的。
任何划分得当、接口严谨的东西,人类都可以理解它,因为咱们的脑袋善于碎片化处理。
智能体和模型也是这个样子。如果它们能够关注于某个模块,而且那个模块的”运行轨迹”相当清晰,模型就不会被这个模块内的主题搞得困惑。这儿我同时用上了”模型”和”模块”这俩词,我要把意思表达得明白。这点非常重要,与我之前讲的”咖啡和电视剧”的论点是一致的。
要是你把所有内容都堆进一个模块,可怜的Agent就会琢磨:”我在这儿到底干什么呢?我该怎样展开工作?”如果你做好了模块的拆分,它的运行效果就会非常棒,就像人类那样。
Matt Pocock:从测试集合中你可能也会获取更高的价值。在这儿我想再问一个相关的问题:我是你的粉丝,与此同时我也推崇John Ousterhout的观点。他提出的”深层模块”(Deep Modules)这个概念,让我深深着了迷。
模块大体上有两种表现形式:一种是”浅层模块”,对外的界面繁杂,内部的落实却很少;另一种是”深层模块”,向外的接口简约,却在内部隐藏了大量的运算细节。我发现这类设计对于Agent来讲是理想的,它们只需要阅读接口,根本不必去搞懂下面的具体实现。
你是否也认可这个看法?你自己在处理问题时,是否也采纳这样的做法?
Uncle Bob:完全同意。模型对接口的字号和布局格外关注。这意味着它们不一定非得深入下层代码去阅读,这既是一种潜在的危险,也是一种益处。只要代码维持连贯一致,问题就不会太大。
它们同时也会审视测试,通过研读测试来领悟系统的运作方式。因此,任何有益于优化代码架构的操作,都能帮助模型更加理解代码。
顺便讲一下,《代码整洁之道》(Clean Code)的后面有个附录,记述了我和John Ousterhout之间的一场冗长的讨论,那真是趣味十足。我不太确定他有没有跟我一样乐在其中,但我自己玩得特别开心。

AI时代,编程规范也需要新的理解
Matt Pocock:我看过你俩在YouTube上的那段完整对话,精彩纷呈,这也是我下定决心邀请你来的缘由。那么,在你那部书里,有什么观点是你现在想要改正或完善的?尤其是像”函数应该保持精悍”这样的主张。
咱们刚才提过,许多东西实际上没什么改变。比如”严苛考验”(Gauntlet)这类模式一贯都很妙,仅仅是过往我们没有足够的劳动力去推进它。
但在这么多原则之中,是否有什么是我们现在需要丢弃的?或是,有没有一些东西需要换一个视角来理解或做调整?我明白这个问题可能会让你感到为难,但你现在有想法吗?
Uncle Bob:首先是涉及”基准值”的问题。在我看来,Agent能够驾驭的复杂性程度,与人类软件工程师的情形并不相同。它们的短时间记忆能力远优于人类,而且特别精准。
所以,我会对CRAP评分(CRAP score)做出相应调适,放松对函数体量的要求。对于人类的编程者,我会把CRAP评分约束在四以下;但对于智能代理,我眼下把阈值设在了六,甚至在思考是否要调到八。
我一直在寻求那个最恰当的基准线,但这并不轻松。
Matt Pocock:在代码行数的范畴,四到八或是四到十二的CRAP评分意味着多少呢?是二十行左右的函数还是一百行的函数?
Uncle Bob:这实际上是由圈复杂度这个因素决定的,也就是一个函数内会有多少种不一样的执行路径。
假设测试的覆盖率到了百分百,那么CRAP评分为六就意味着这个函数有六种执行路径,而且每一种都被测试所验证过。这其实正是CRAP评分的关键意旨:保证覆盖率的充足,同时把复杂度约束住。
我曾经和Agent在这个话题上有过许多次的争执。当然,你没法绝对相信Agent在争辩中的观点,不过我还是会和它们讨论。从它们的意见看,六似乎是个相当不错的阈值。
另外,还有别的一点。在我的书中,我阐述了许多”纪律规范”,比如说测试优先驱动开发(TDD)。我当时是TDD的坚强拥护者,可那其实是一类”人类的纪律”,是根据人类的思维办法演生出来的。
所以,我根本不打算,也不会把这类纪律加强在Agent头上。我觉得,强制Agent写一行测试,再写一行生产代码,再写下一行测试,这没有太多意义。对人类来讲,这样的好处巨大;但对于Agent,我更倾向让它们循着John Ousterhout的办法走:先撰写函数,再给函数编写测试。
事实上,即便我规定Agent严苛遵守TDD的纪律,它们最后也老是会回到”先写代码、后编测试”的模式。
因此我得出结论:把人类的纪律强加给智能Agent,恐怕是个失误。咱们无需强制实行纪律,不过需求坚守”人类的道德”,仅仅是具体的基准值可能要按智能代理的属性做出调整。
Matt Pocock:这确实和短期记忆的容量相关。针对短期记忆空间有局限的人类,测试驱动开发(TDD)效果显著:你仅仅要记住充足的细节,先写成一条测试,再记住怎样让测试成功,然后就能停下来歇一会,比如喝杯咖啡。
既然我们已经讨到了”做正确的事”以及系统架构设计,那么在真正把工作交付给智能代理以前,你通常会进行多少筹备和计划?在让Agent开始执行、进行测试和检验以前,你会投入多少准备工作?
因为如果一开始分配给Agent的工作自身就是有问题的,或者需求本身尚未清晰界定,那无论后面花多大的力气,都只会浪费时间。
Uncle Bob:眼下最大的诱惑,就是编写过度的规格条款:让工程师不停打磨需求和筹划去编制规格说明,再把所有的东西一股脑儿交付给Agent。这是源自70年代的一个老陷阱,后来瀑布式开发方式就是这一思路的代表,敏捷开发方式的出现,相当大程度上正是对这种做法的反思与纠正。
过于沉重的初期筹备,往往会把事搞得乱糟糟的,因为最后产出的产品,险些不可能和最初的设想分毫不差。
面对智能代理,社会上同样容易栽进这个坑。这个礼拜我自己就尝试了这类办法,结果一次又一次证实这是一场灾难。作为人,你会飞速意识到,Agent根本没法绝对按照你设定的方案去执行。由于你不可能提前思考每一个细节,而Agent又没有你那般的判力,所以它们容易朝着错的方向一路跑下去。你只好喊停,回溯重新制定方案,再从零开始。
所以我已经放弃这样的模式了。我们最好还是采纳敏捷的方向:先让Agent去完成一两个具体的任务,然后看看整体的架构是否有难题;假如存在,我就手工介入,进行一些微调,再让它继续完成后续的任务。
我们大概永远无法完全避免最后这一步”人力梳理”的工作。虽然我一直在想办法消除这个环节,可眼前还不确信它是否确实可行。
Matt Pocock:我把这看作是一个劳动力配置的课题。
往常,打造一个产品可能需要周把时间甚至数个月。现在,开发的速度大幅提高了,然而初期的筹备和后期的审视花费的时间并没有获得对应的降少。咱们期望工程师能够高频率地进行迭代,但”把东西做坏了”这类事儿仍然可能产生。
我察觉到,现在好多人开始实施一种”最大化规划”的办法:他们拿着一份需求文档,让七个不一样的Agent各自运行一遍,尽力完善计划,然后才正式进入执行。
可这听起来好像并不是什么绝妙的办法,对吧?
Uncle Bob:Agent极其喜爱去编制计划。天哪,它们简直是沉溺于写计划。它们会不断进行装饰,把计划打磨得更加华丽、更为完备,细节也越发丰硕,却到了真正开始做的最后环节,往往还是会崩盘。
现下全部业界都能观察到这类倾向,也就是所谓的”需求驱动型开发”(Spec-Driven Development,SDD)。可我的直觉是,这条道根本走不通。
咱们应该重新审阅敏捷开发最根本的思维:做一个小部分,获取反馈,再对方向做出调试与重新梳理,继续做一个小部分。
我以前在做敏捷开发讲座时,经常举这么一个例子:假定改建一栋房屋的成本只要一美元——包含打地基、修屋顶,以及之后一切的修改,每次都仅仅一美元。你会怎样盖这栋房子?
你会不会先花数千美元请一位建筑设计师出一份完美的计划,之后再花一美元交付给承包商,一次性地把房子垒出来?
还是直接走到承包商前面说:”我想在这儿打地基,弄成这样的形态。呃,不对,这样不好,改一下。厨房放这儿,起居室放那儿。等等,还是把它们反过来吧。”
显然后一种办法更加理性。而眼下,软件做改动的成本已经大幅下降,差不多接近于零了。在这样的状况下,我们为啥还要投入大量时间与精力去做价钱高昂的前期筹备?
为啥不直接去不停地尝试、调适和改进,直到最后看起来没有难题为止?
Matt Pocock:我特别赞同。我对”需求驱动型开发”这个标签自己持有很大的意见。到底什么是SDD呢?提示词优化(Prompt Engineering)该不该算呢?要是你像以往那样对团队伙伴讲:”修一修页面加载的难题”,这是需求驱动型开发吗?这有算得上一份需求文档吗?你会把这类说法持久化在源代码库内吗?
Uncle Bob:不,我不会。需求规格说明是过渡的,它会消失,也会快速变动。需求文档与源代码不是一回事。过往人们常讲,”人工编写了源代码,因此代码就成了最终的需求说明”,可当下这个讲法已经站不住脚了。
源代码仍然存在,仅是已经不再是人工去撰写。许多人因为这一点感到失落,认定必需存在某种人力界定的东西摆在最前头。但往下看,即便是Agent生成的产出,原始也是由人类去驱动的。
我眼下的做法是,不再编写一份用来定义”我需要什么”或者”我具有什么”的需求规格。我直接去看最后的成品,那个成品本身就可以当作需求说明。
我有非常多的工具,比如针对Clojure、Java和Go的CRAP工具、变异测试工具,以及Agent Harness。我会告诉人们:不要直接去下载我的工具,由于那是我给自己写的。你应该让你的Agent去学习这些工具,随后让Agent根据你的要求量身定做一个。
我觉得这才是更为上乘的办法:真实地界定事情的本质,再根据具体要求做个性化的调适。
Matt Pocock:关于Agents,我一直觉得挺有意思的一点是:假如你把东西发给它们,它们真的会去细读。这和人类很不一样。
如果你给某人分发一份细致的技术文档,你可能也就只能得到百分之二十的阅读率——而百分之二十恐怕还是说高了,也许仅仅百分之五。可如果你把文档交付给智能体,它们大体上真的会全部读完。
Uncle Bob:反过来也一样,Agent创作出来的东西,人类反而就不想去读了。

给年轻开发者的建议:AI再强大,编程的底线永不过时
Matt Pocock:确实是这样。它们期待我们把它们写的全部东西都读掉,但咱们根本不会去读。有点搞笑的是,这彻底是一种失衡的互动。
往后看的话,我极其想弄清楚,因为咱们已经习惯了跟智能体的沟通交流,人类彼此之间的关联会发生如何的转变。我自己早就习惯了和智能体闲聊,就像在同朋友叙旧一般。我真的很想知道,也很沉溺于看到,伴随时间推移,人生会怎样去效仿创意。
最后我想问你一个相当有分量的问题。我想重新引用一下John Ousterhout的看法,由于他对编程的不一样类别有个很好的划分:战术型编程和战略型编程。
战术编程就像前线的军人,是那位真的置身战场、卖力投入具体军事行动的人;战略编程则更像是司令,做的是更高层面的指挥整个战役。我相信咱们都同意,这是认识编程不一样类别的一个极好的构架。
而眼下的难点是,智能体在战术方面特别擅长,却在战略层面特别吃力。
那么,针对那些刚进这一行的人,既然AI已经吃掉了许多战术方面的工作,他们该咋样去学战略编程呢?这也许是数不清的人都想从你这儿听到的回答,Bob。他们期待你把你的脑筋”借”给他们,让他们理解为什么变异测试如此紧要,或者应当怎样去筹备和掌握这些模块。在某个意义上,这正是你如今正在进行的工作——把自己的体验和思维分享给许多人。
Uncle Bob:这个提问我很频繁地碰到。我并没有一份完美的解决方案,由于我真的不知晓,然而我是这样想的。
首先,编程者习得编程的渠道(无论在大专院校还是别处)都应当是写代码。你需要先花上一年时间去写代码(具体期限我也讲不准),这样你才能切实晓得Agent在处理的到底是什么。
下一步应该是,当你加入一家大量采用Agent的单位时,作为一个才完成训练的青年,你应该被视作一个Agent。那位辖下运营许多智能体、自己肩负战略选择的主任工程师,应当把你当做一个智能体。他应当分配和智能体相同的工作任务,让你承受和智能体相同的确定性工具的约束。你应该在这样的状况下停留若干个月,虽然生成的内容会相当微弱,但学到的东西会极其丰富。等你通过了这种严峻的磨炼,也许你才会被许可去亲自操控一个智能体。你未必能够完全舍弃代码。
十年往前,我常常告知人们:假如你从未写过汇编语言,那就需要花个周末去试试,这样你才能明白后台到底在进行什么。假如你全天候只写Java,那你便生活在一个梦幻里,那儿仍旧存在好多你没搞懂的”魔术”。花个周末码汇编,你最终就会理解这所有到底怎样工作。
我认为这个道理今天仍然建立。在这条进修之路上,你必需从最原始的东西——机器码——开头,一路经历汇编语言、比如C这一类的初阶编程语言、像Python这样的高级语言,再走进处理智能体阶段的工作,学会采用确定性工具。最后,你才能在指导下,真实地开始战略性地运转智能体。
Matt Pocock:但这很不简单。智能体好似是代码之上的一个抽象等级。你提出的那些抽象等级,比方说模块检视器,就特别有意思。这确实是一种极好的进修法子:在代码上层建构这么些抽象,这样当你真的深入研究时,反而能获取更深层的认识。
然而要是一个人只做战术性的工作,作为公司的观看者,你或许会瞅着他琢磨:”咱们干啥要招他?我手下有Uncle Bob的五个人加强小组——也就是五个智能体——花费仅仅他的零头,却能做得更优越。”
所以我始终在思索,人们到底怎样才能获得这种能力。因为传统上讲,战略型编程的反馈链条特别久。一个人如果干了半年就离职了,他或许永远都没法学会战略型编程,由于他犯下的纰漏,也许到了九个月以后才会浮现出来。他完全看不到自己的问题。
但有了智能体,现象就不一样了。由于万物都被大幅加快了,你实际上能够更快地得到有关过失的反馈。
那么,你是怎样判断出你的智能体在出问题的?在去年十二月份,当你看着它们写出来的东西,感觉”这糟到掉渣”的时刻,你是怎么判定的?
Uncle Bob:早期的日子,我只是看着代码,然后发现里头那些”不优秀的代码”。但那其实不是最紧要的部分。
更加紧要的是下一个步骤:我会看着它们在无功地忙乱。我能看出Agent什么时候在竭力对付,因为我自己也曾经过相同的困顿。难点在于,一位方才入职的工程师,大概根本没法识别出这类困顿。
我是通过艰苦的事践学会这个道理的。其实,关乎这个课题,那些老著作里有特别丰厚的东西——仅仅由于年月长远,而今没人读了。可这些著作真的非常出众。你能够去读Tom DeMarco或Ed Yourdon的著述。
Matt Pocock:还有《程序员修炼之道》(The Pragmatic Programmer),全部那些经典读物。
Uncle Bob:是啊,《程序员修炼之道》便是里头之一。实际上还有数不胜数这样的老著作,它们全都非常超卓。假如你在年轻时用心阅读过这么些著作,就会渐进地对更高层面的战略博弈形成直感。
自然,你需求过滤掉内里一些已经落伍的内容,因为许多著作写作于20世纪七、八十年代。不过趣味的地方在于,许多重大的经历与教导,偏偏便是在那一阶段总结出来的。
所以,这是我最初会提议的进修路线:先通过研读这些著作创建基础认识,随后再通过亲身实战真正掌握它。我也恰恰因此,信任他们应当先”扮演”数个月的智能体,亲历一下智能体所处的处地,这样才能真实地体会那是什么滋味。
Matt Pocock:变成智能体。让智能体把工作委任给你。你变成了智能体的子智能体。我很赞同这个想法。听上去,编程的基础仍旧至关紧要。为什么会如此?针对那么些说基础不紧要的人,你会怎样回复?
Uncle Bob:编程基础之所以紧要,是由于它一直都非常紧要。我觉得这句话是迪杰斯特拉(Dijkstra)讲过的:程序设计是人类时至今日测试过的最复杂的东西,比咱们做过的任何别的业务都更为复杂。
因此,基础其实是咱们梳理这类复杂性的一套方式,让复杂的物件变得能够理解——不仅是让人类能够懂,也让咱们的模型能够懂。总之,模型到底也是仿照人类建造出来的。
基础之所以在当下仍然有效,就是因为这是咱们梳理复杂度、认识复杂度的方式。现在有一些人觉得编程基础早就无关紧要了。他们会尝到苦头,而且不会等很久。虽然可能会比我想的更久一些,由于智能体真的很厉害。可我早就见过它们碰墙的时刻,我知晓那面墙就在那儿,所以我不想再碰一下。
这其实是一个相当有意思的演化过程。你方才提及了抽象等级。咱们现在早就立在编译器之上了。过往,咱们的抽象等级是编译器;再以前,是汇编语言;更往前,是机器码。眼下,这个抽象等级又向上攀升到了模型这一层。
而每一次抽象等级向上提升,处在较低层级的人都会诉苦。他们会讲:”这会毁坏一切。咱们甚至都没活儿干了。编程变得这么容易,五岁的小娃都能码代码了。”
每次都是同样的故事。眼下,咱们又走入了下一个阶段,下层的人又开始喊:”这会毁坏一切。”
不,它不会。
同样的规律仍然适配,所以的基础之所以存活,都是出于同一样的缘故。你今日舍弃的那些规则,一年以后,极有也许还是会从地下把它们捡起来,拍掉身上的灰,然后再一次想起:为什么一开始需求这么些东西。