夜雨聆风学习资料网

ARTICLE · 1001975

Uncle Bob:AI 时代,软件基本功为什么仍然重要?

Uncle Bob:AI 时代,软件基本功为什么仍然重要?

# Uncle Bob:AI 时代,软件基本功为什么仍然重要?

当 agent 写代码的速度远远超过人类,我们还需要 Clean Code、模块化、测试和架构吗?

Robert C. Martin(Uncle Bob)的回答不是简单的“需要”。他真正想说的是:基本功的目标没有变,但执行这些基本功的主体、工具和成本结构都变了。

过去,人靠纪律约束自己;现在,人要为 agent 建立确定性的护栏。过去,变异测试、复杂度治理等方法常常因为太慢而被搁置;现在,agent 恰好可以接手这些枯燥而昂贵的工作。过去,程序员在代码里积累判断;现在,新的问题是:当新人不再亲手经历那些挣扎,他们要从哪里获得战略判断?

这期对谈围绕的,就是这条变化中的分界线。

//先说结论

  1. 1. Agent 同样会被烂代码拖垮。 它的承受阈值也许高于人类,但混乱积累到一定程度后,同样会变慢、反复打转,甚至放弃(10:47)。
  2. 2. 能写成检查器的规则,不要只写在 prompt 里。 长上下文会稀释规则;测试、lint、复杂度检查和依赖约束则能持续给出明确结果(13:44、16:21)。
  3. 3. Agent 的速度让一批“原理正确、人工成本太高”的旧技术重新可用。 Bob 重点举了 CRAP 分析和变异测试两个例子(04:19、19:17)。
  4. 4. 要把人类的价值强加给 agent,不要机械照搬人类的纪律。 测试、低复杂度和清晰结构仍然重要;严格 TDD 的节奏及复杂度阈值,则需要按 agent 的能力重新实验(33:15)。
  5. 5. 架构判断和新人培养仍然没有被解决。 Bob 目前仍亲自设计模块与依赖方向;他对新人路径的建议也明确只是猜想,而不是经过验证的培养方案(26:12、46:42)。

//嘉宾与主持人

**Robert C. Martin(Uncle Bob)**是软件工程师、作者,也是软件工艺与工程基本功最有影响力的倡导者之一。代表作包括《Clean Code》(《代码整洁之道》)、《The Clean Coder》(《程序员的职业素养》)和《Clean Architecture》(《架构整洁之道》),分别从代码、职业纪律和系统结构讨论如何控制软件复杂性。作品信息可参见 Pearson 的《Clean Architecture》介绍 与 O’Reilly 的《Clean Code》书目页。

Matt Pocock是 TypeScript 教育者,代表作是课程、教程与图书体系 Total TypeScript。此前他参与 XState 核心团队,也曾任 Vercel 开发者倡导者。本期他不断把 Bob 的经验压到几个可以检验的问题上:为什么烂代码仍会拖慢 agent、规则为什么失效、规格是否还值得保留,以及新人失去战术训练后如何获得战略判断。

对谈中还有一个重要的思想坐标:John Ousterhout 的《A Philosophy of Software Design》。Matt 用书中的“深模块”和“战术式/战略式编程”追问 Bob;Bob 则借它解释为什么模块边界、窄接口和长期设计判断对 agent 仍然重要。

//关于这份整理

  • >
    • 原节目:LIVE: Uncle Bob on Software Fundamentals in the Age of AI
  • >
    • 主持人:Matt Pocock
  • >
    • 嘉宾:Robert C. Martin(Uncle Bob)
  • >
    • 发布时间:2026 年 8 月 19 日
  • >
    • 时长:56 分 39 秒

下面保留全部 75 个话轮的中文精校译文,连续覆盖 00:00–56:39。为适应中文移动端阅读,发布版不重复刊载整篇英文;文末金句保留英文原话。口语中的犹豫、重复、自我修正和不确定性尽量保留,因为它们本身就是判断边界的一部分。

//阅读导航

  • >
    • 00:00 开场:浴袍里的 Uncle Bob
  • >
    • 01:40 从 12 岁写代码到《Clean Code》
  • >
    • 04:19 当 Uncle Bob 遇见编程智能体
  • >
    • 12:11 规则会软化:“它们更像是参考准则”
  • >
    • 13:44 Lost in the Middle 与确定性检查
  • >
    • 17:56 多智能体流水线:并行与单一职责
  • >
    • 26:12 架构与规划:先拷问智能体,再动手
  • >
    • 29:55 Ousterhout 与深模块
  • >
    • 31:15 复杂度阈值、CRAP 分数与 TDD
  • >
    • 36:09 对规格驱动开发的怀疑
  • >
    • 44:07 智能体什么都读,人类什么都不读
  • >
    • 46:42 新手如何学:先写代码,再看智能体挣扎
  • >
    • 54:35 尾声:抽象层之上,回到浴袍

//完整对谈

//开场:浴袍里的 Uncle Bob(00:00–01:40)

Matt Pocock|00:00

今天给大家准备了一份大礼。我请到了一位我想聊很久的嘉宾,他深耕软件领域的时间,可以说和我当开发者的年头一样长——不,比我当开发者的年头还要长得多,而且现在他正在 agent(智能体)领域大展拳脚。我们有请 Uncle Bob(鲍勃大叔)Martin,穿着浴袍现场连线,准备开聊。Uncle Bob,你好,欢迎。

Robert C. Martin|00:26

你好,谢谢。很高兴来到这里。

Matt Pocock|00:30

你能来真是太好了,太好了。对于那些不太了解"浴袍"这个梗的朋友,我们也许应该补充说明一下。这是怎么回事?为什么浴袍成了你形象里很重要的一部分?

Robert C. Martin|00:43

我想是两年前的事。当时我穿着浴袍坐在前门廊上,早上六点。我开始琢磨 SQL 有多糟糕,因为那些 SQL 注入(SQL injection)攻击。搞一门文本语言来做数据库访问,从安全角度说根本说不通。当时我穿着浴袍,直接掏出手机就开骂了。这就成了"晨间浴袍"这个品牌。显然大家很喜欢,所以我就又拍了几条。

Matt Pocock|01:16

所以你今天是带着这种心情来的吗?这就是传说中的 Uncle Bob 大放狠话环节吗?

Robert C. Martin|01:21

现在是大清早。我还没喝咖啡,整个人还有点迷糊。

Matt Pocock|01:26

早上六点就在琢磨 SQL,哥们。

Robert C. Martin|01:29

早上六点,早上好。这事儿现在就算翻篇了。其实现在已经是上午十点了,浴袍已经脱了,不在我身上了。我正在喝今天第一罐健怡可乐,所以我状态还行。

//从 12 岁写代码到《Clean Code》(01:40–04:18)

Matt Pocock|01:40

很好。嗯,现在 polo 衫登场了。Uncle Bob 是个什么来头?是这样的,我这边观众里可能大多数人都是开发者,但也有一些不是。我们该怎么向大家——尤其是非开发者——介绍 Uncle Bob,介绍"Uncle Bob 现象"呢?

Robert C. Martin|01:59

"现象"这个词,我可不敢当。我是个程序员,当了很久很久的程序员,到现在已经超过半个世纪了。我的第一个程序是在 1964 年写的,那年我 12 岁。那是一台小小的模型计算机,我母亲买给我当 12 岁生日礼物的。我通过把白色小管子插到插栓上来给它编程。它本质上就是一个三位的有限状态机(finite state machine),但 12 岁的我被它迷得不行。

Matt Pocock|02:38

好。所以你当时 12 岁。我们怎么跳到 50 年后,也就是现在正做着自己这些事的 Uncle Bob?

Robert C. Martin|02:44

对,比 50 年还多一点。我想想。嗯,我就是开始尽可能多地学编程。我父亲给我买了一本 Fortran 的书、一本 COBOL 的书、一本 PL/1 的书。我全读完了。但没有机器可以运行任何东西。

所以我把程序写在纸上,在脑子里执行。16 岁那年找了份写点代码的活,不过那只是临时的。然后 18 岁那年我找到了一份正式工作,从那以后我就一直是程序员。

Matt Pocock|03:17

所以你在这个行当的一线摸爬滚打了很久,而且你还写了一本——我觉得——相当重要的书。

Robert C. Martin|03:22

几本吧。其中一本,我觉得,挺重要的。我还写过好几本别的,不过对,就那一本。

Robert C. Martin|03:28

好像一下子就火了。那本还不错。对。然后是关于代码整洁的,如果我没记错的话

Robert C. Martin|03:34

没错的话,对对对对。这是第二版。这是第二版。

这是第二

Matt Pocock|03:41

版。但是,你知道,我的意思是,它可能是——至少在我跟人聊好书的时候——在软件工程这个领域,它是被提起次数最多的那一本。就是说,它极其受欢迎,影响力极大。所以,这也是原因所在。我今天想跟你聊,是因为你在 AI 之前的时代产生了这么大的影响,而且你几乎从那个时代存在开始就一直身在其中。那么,Uncle Bob,现在 AI 已经横空出世、成了现实,对你来说,事情有什么变化?

//当 Uncle Bob 遇见编程智能体(04:20–12:11)

Robert C. Martin|04:19

这事儿让我有点意外,大概是去年 12 月,圣诞节前后。我就是随便玩玩。我之前已经玩过一阵了,你知道的,ChatGPT,还有一点点 Grok,东一点西一点。但我没觉得有多厉害。然后有段时间我开始想,嗯,也许这些东西比我想的要有意思一点。我弄了一个 agent(智能体)。我拿到的第一个 agent,我想是早期的 Grok。是一个早期的 Grok。我就让它给我写点代码,它写得挺烂的,但好歹把代码写出来了。当时我手上正有一个项目。所以我说,嗯,也许这东西能在这个项目上帮我一把。我就开始让它跟我一起做这个项目。结果我一直在给它收拾烂摊子。它总是,你知道的,总是弄得一团糟。总是留下一点狗屎。我就想,你知道的,这东西有意思,因为它快;但它也让人恼火,因为它让我变慢了。就是这样。然后我开始想,等等。正因为它快,它能做我做不到的事。早在 2000 年代初,有几项创新吸引了我。

我当时想,哦,这些是好点子。但它们完全不实用。其中一个叫 CRAP。它是个缩写,我能说什么呢?它是——它是——它是一种利用代码覆盖率的方法。就是说,你会对你的代码跑代码覆盖率、测试覆盖率。然后你还要测量每个函数的圈复杂度(cyclomatic complexity)。你把这两样东西混进一个复杂的公式里,就会得出一个分数。这个分数衡量的是你的函数有多烂。早在 2000 年代初,我就想,这主意太棒了。我在当时做的一个大项目上跑了一下。果然,有一堆烂函数。但接下来我要花没完没了的时间,挨个过这些函数,想办法修它们、重写测试。而代码明明都跑得好好的。所以我觉得我实在负担不起这么做的代价。所以它虽然有意思,我还是把它搁一边了。另一项创新叫变异测试(mutation testing)。

这个也很吸引我,因为在变异测试里,你有一个小程序扫过你的源代码,把负号翻成正号、把小于号翻成大于号、把等号翻来翻去,还有别的改动。每翻转一处,它就把你整个测试套件跑一遍,并且预期测试套件会失败,因为它显然改动了重要的东西。如果没失败,那这就是一个存活下来的变异体,必须把它杀掉。后来我又在 2000 年前后那个项目上跑了一次。我不得不跑一整夜,因为测试套件跑一次要四分钟,而我要跑上大概几百次。但它确实找出了一堆存活的变异体,我都修掉了。不过同样,它不实用。我没法把它放进正常的构建流程里。

于是去年 12 月——也可能那会儿已经 1 月了——我坐在那儿想,等等,这些家伙很快。它们不在乎活儿有多无聊。而且我叫它们做什么它们就做什么。那为什么不让你把你刚写的东西全跑一遍 CRAP 呢?它就跑 CRAP。然后它就会清理代码。我看着它干这些。我想,嗯,这挺酷的。那为什么不也跑一下变异测试呢?它就跑变异测试。也许只花 30 分钟,而不是一整夜。然后它会把所有漏洞都堵上,确保每处都有测试覆盖。我就想,你知道的,这也许是清理狗屎的一个办法。这些东西会留下大量残渣和碎屑,但也许这就是清理所有狗屎的好办法。于是我沿着这条路继续走,加更多工具,继续和这些 agent 合作。我把它们调教到了一个干得相当不错的程度。所以现在,我的原则是,你知道的,我让 agent 干活。我让它们跑这些工具。而且我会非常努力地达成一种状态:我根本不用看代码。我不信任它们做的事。我会用别的方式来验证代码是不是还算体面。

你知道的,我会看 CRAP 分数,确保它们够低。我还会时不时对代码做抽查。我还跑一大堆其他的测试。但总的来说,这就是我的目标。如果这些东西很快。它们确实快。而且如果我能约束它们把活干好,那我就不会把我的慢强加给它们。我的慢,就是以某种细致程度去 review 代码、检查代码。它们写代码快。我写代码慢。

所以我会把代码交给它们。我去处理代码周边的那些事,确保一切没问题。到目前为止还

Matt Pocock|09:54

所以你的目标是开始把自己从代码里抽出来,从人工 review 里抽出来,在代码周围搭起一套脚手架(scaffolding),这样你自己就不用怎么碰它,也让 agent 被约束得越死越好——像穿上最紧的紧身衣(straitjacket)一样——让它没法犯错。我觉得,这里面有一个假设,我想先聊聊,就是——我想先聊聊的是——"dog doo"这个词,对吧?用英式口音念出来可没那么好听,dog doo,你知道的。那么,为什么——为什么 dog doo 是坏事?为什么烂代码是坏事?比如说,我们为什么要在乎这个?因为这些东西这么快,推进得也这么快,我们为什么还要在乎?为什么还要费心折腾所有这些

Robert C. Martin|10:47

这套束缚装置(harness)?我们难道不能一路硬推下去,直到 bug 自己消失吗?我很早就注意到一件事,大概就在 12 月前后:我会——我会跟那个,比如那个小小的 Grok agent 一起干活,我让它做一件事,它就会留下一堆烂摊子。我不清理这堆烂摊子,反而让它做下一件事。它就留下更多烂摊子。然后我又让它做下一件事。我注意到它变慢了。我注意到它开始吃力了。它会进入一种模式:改了一个东西,却无意间弄坏了另一个。然后它不得不去修那个,又无意间弄坏了另一个。开始原地打转。我就想,好吧,这些 agent,它们也许很快,也许相对聪明,但它们和人类一样,会受乱代码的拖累。当然,也许没那么容易受拖累。也许阈值不一样。但阈值还是存在的。代码可以乱到 agent 再也没法应对的程度。

然后它们就会开始空转,把烂摊子弄得更糟,并且无能为力。我真的遇到过它们直接放弃。有一个 agent,有一次说:我实在处理不了这个了。我这是转述。它没用这几个词,但显然当时就是那个情况。

//规则会软化:"它们更像是参考准则"(12:12–13:44)

Matt Pocock|12:11

嗯。那你对此的回应就是:我该怎么把这堆烂代码收拾干净?我觉得大多数人遇到这种情况时,第一反应就是往 agent 里塞指令。对吧。他们会说,好,我要往这个做实现的 agent 里灌信息,对吧?我要往 CLAUDE.md 或 AGENTS.md 里堆东西。每次我看到什么不好的输出,我就加一条指令。而你用的不是这种方式,你用的是确定性机制(deterministic mechanism)。所以你用的是自动化检查,而很多其他人用的是——用我的术语来说——「steering」(引导),对吧?他们试图去引导它。那你为什么不,你到底有没有在做 steering?

Robert C. Martin|12:52

或者说,这在你脑子里是怎么运作的?其实一开始我用的就是那种方式。我是说,我早期的 prompt 是这样的:这是你做 TDD(test-driven development,测试驱动开发)的方法。这是你写 clean code 的方法。你的代码应该长这样。

你应该遵守所有这些规则。然后呢,你最后会整出一份五页、十页长的文档,里面描述着关于好代码的一切好东西。我想我甚至可以把那本该死的书整个喂进去。但我注意到的是,这些 agent、模型,不管你叫它什么,它们对待这些规则的方式,是《加勒比海盗》里那种意义上的。

Matt Pocock|13:31

更像是「参考建议」,你知道,也许遵守一下。我能不能,我能不能把这句话记在你头上?这也正是

Matt Pocock|13:38

我脑子里冒出来的那个比喻。所以我很高兴我们在这点上想到一块儿了。是的,好吧,那它们确实

//Lost in the Middle 与确定性检查(13:44–17:57)

Robert C. Martin|13:44

会打折扣。这背后是有技术原因的。我做了一些,不算很多,但做了一些研究,去看模型为什么会这样表现。结果发现有一种现象,叫「lost in the middle」(迷失在中间)。就是随着模型里的上下文窗口越堆越多,出于技术原因,最开头和最结尾的内容会比中间的内容更受重视。但结果就是,agent 会忽略中间的内容。而你在最开头写的任何东西,如果内容很长,就会被挤到中间去,对吧?所以也许你放在开头的前三句话还能保住优先级。但里面的第 50 句、第 80 句,就没了。它们就埋没在中间某个地方。而可怜的 agent 要应付这个不断膨胀、不断膨胀的庞大上下文,它试图把重要的部分捞出来,而中间的东西就这么没了。所以确定性工具(deterministic tools)就不会以这种方式消失。我认为用 agent 的关键——这事真的很难做到——关键就是把最初那段 prompt 精简到绝对的最小值

Robert C. Martin|15:05

这样你才能让它尽可能多地留在优先级区域里。对。对吧?然后再用确定性的

Matt Pocock|15:14

工具做事后检查。完全同意。我把这个叫做上下文窗口的「聪明区」和「变笨区」(smart zone and dumb zone)。这不是我发明的词,是 Dex Horthy 的说法。我偷来的,而且这说法非常、非常好。就是说,在上下文窗口的早期阶段,比如前 15 万个 token,它还挺聪明的。但随着你往下走,transformer 里的注意力关系就变得非常紧张。被稀释得很厉害。就像所有人都在喊。每个 token 都在一个拥挤的房间里喊,而房间越来越挤。对吧。噪音太大,你根本听不清信号。

这对我来说完全说到点子上了。所以接下来就引出了,你算是放弃了 steering。然后你算是回过头去用一些自动化检查的老技术。那么听起来,因为这些检查不会像 steering 指令那样进入上下文窗口,你差不多可以把它们一层层往上叠,对吧,叠了一层又一层。所以如果你用的是一门强类型的语言,再加上测试之类的东西,在你看来这幅图景是什么样的?自动化检查会不会有「太多」的时候

Matt Pocock|16:15

?这正是我现在非常努力想搞清楚的问题之一。

Robert C. Martin|16:21

显然,肯定存在一个「太多」的情况。最终,你会把 agent 拖慢到比人还慢的地步。到了那一步,你就输了。那还干嘛要做?但只要你能保持生产率比人高出一块,你就还是领先的。而就我目前看到的来说,我,你知道,我能拿到这个余量,你知道,大概是两倍、三倍、四倍的样子,对吧?它们确实还是跑得挺快的。不过,我,我确实把它们拖慢了不少。当、当、当、当你使用这些确定性工具时,你实际上在做的是把它们放进一个循环(loop)里。现在很多人喜欢谈循环。你把它们放进循环里,然后说:好,你必须、你必须一直改代码,直到这个工具说通过了为止。于是 agent 就一圈一圈地转。好,我得做这个。我得做那个。我得、我得在这边多加些测试。我得降低圈复杂度(cyclomatic complexity)。我得把这些函数拆开。

我得做完所有这些工作,这得花它一阵子,直到代码全部合规为止。所以你是,你是在牺牲生产率来换取更高的质量。而在某个点上,这个交换肯定不划算。但我还没找到那个临界点。所以现在我正在尝试让多个 agent 互相交流、互相交接,这样一个家伙做一件事,下一个来 review,再下一个来测试,再下一个来做加固,等等。这里面的沟通开销大得离谱。然而它仍然

//多智能体流水线:并行与单一职责(17:57–26:12)

Robert C. Martin|17:56

比人快得多。我们来聊聊这个。我们来聊聊多 agent 系统

Matt Pocock|18:03

因为我觉得这个特别有意思。我一直有点——而且我觉得这可能正是我们看法不同的地方——我一直对那种人有点怀疑,就是那种说"我把员工全裁了,我现在有一百个 agent,每个都有不同的角色,它们互相交流,还各有自己的邮箱账号"之类的人。你知道,那些全是扯淡。对我来说,真正管用的——好吧,在实现(implementation)加评审(review)这件事上,我算是破个例,对吧?我觉得那里的好处并不是有两个非常专业化的 agent。而是说,好,你来做实现,这有点像 red-green-refactor(红-绿-重构)的思路:实现者(implementer)要做的只是,比如说,写出很烂的测试,然后让它跑通。它不需要把它写得多漂亮。

然后评审者(reviewer)进来,它不需要做探索,因为它手上已经有实现者给出的 diff 了,它非常清楚自己在评审什么。也正因为任务约束小得多,你还能往里塞相当多的引导指令。所以我很想听听你的看法:实现者做什么,评审者做什么,还有那些 hardener(加固)agent 又是干嘛的。这个我还没试过,多给我讲讲。

Robert C. Martin|19:17

像这样用多个 agent 有两个好处。一个是可以并行跑。比如说,你可以同时跑三个 coder(编码 agent),而我那台小笔记本能支撑的远不止三个。另一个好处是,当你把每个 agent 聚焦到单一任务上时,你就把上下文窗口(context window)控制住了。"lost in the middle"(中间迷失)的问题就没那么严重了。这样你可以在开头多堆几条规则——不能多太多,但可以多几条——而且它们会更愿意遵守。你还可以搭一个系统,让 agent 生出来、干完活、然后死掉,这样下一个进来的 agent 带着干净的上下文。这些就是好处。坏处是启动时间很长,对吧?一个 agent 光启动就得花个十秒、十五秒。然后它还得从头把自己的整个上下文重新搞清楚一遍。所以这部分启动开销是免不了的。我喜欢尽可能把任务切细。

所以我会跑一个 specifier(规约 agent)。specifier 的工作是拿一份人写的文档,把它转成 Gherkin 和一份 QA 流程。Gherkin 嘛,就是 given-when-then 那一套,是一种高层的验收测试。而 QA 流程本质上就是系统测试——你拿着 QA 流程,通过 UI 把系统整个跑一遍。而且我让它们从人的视角来写:你是一个人类,你正在 UI 上操作这个系统,你必须证明系统能工作。我会产出这两份文档:Gherkin 文档和 QA 文档。然后这些喂给 coder。coder 的工作是写单元测试,写实现所描述 story 的代码,还要把 Gherkin 跑通。这是它必须做的。跑通之后——coder 会在上面忙一阵子——东西就交给 cleaner(清理 agent)。

cleaner 的工作是跑烂代码分析(crap analysis)和一般的代码评审,把它清理干净,把实现者留下的烂摊子收拾干净——因为到那会儿实现者肯定已经弄得一团糟了。然后我再让它进入 hardener(加固 agent)。hardener 这家伙跑变异测试(mutation testing),而且它绝对毫不留情,对吧?它会对代码做变异,要求 100% 覆盖率,每个等号、每个小于号都不放过。这个活很耗时间,但最后会吐出来。接着进入 QA agent。QA agent 拿着写好的 QA 文档,把它变成一个可操作系统的可执行脚本,得出一个确定性的结果。如果你能把这一整套都走完,你得到的就是一个相当能用的程序了。我用这套方法很成功。

如果给它一个任务,单个 agent 五分钟就能做完、但结果存疑,那这套流程大概要花一个小时——走完所有环节大约一个小时。但这依然是赚的。因为你知道,换成人来做大概要半天。所以我大概获得了,比如说,四倍、五倍的生产率提升,而且质量非常高——比人愿意投入的质量高得多。还不止如此,你这是在——你是在提前投入生产率,以后再把它赚回来。

Matt Pocock|23:06

对。这是对你自己代码库的一种投资。嗯。我觉得特别有意思的是——这是我最近一直在想的事——你操纵的不只是上下文窗口。还有一个概念,就是一个上下文窗口的"轨迹"(trajectory),对吧,一个会话的轨迹:如果你让 agent 做一件事,并且往某个方向引导它,那么在同一个会话、同一个上下文窗口里,后面发生的一切都会继续沿着这条轨迹走。所以如果你让它说"好,也许这里我们应该测一下 UI",那么之后每一次它都会再测 UI,不管你让它改多少次。而清除这条轨迹的唯一办法就是清空上下文窗口,对吧?所以如果你是一个实现 agent,你的目标只是让它跑起来,那你的轨迹就没那么——没有那个要确保 100% 覆盖率的 agent 那么苛刻。我觉得这点特别有意思。这个——这跟你的心智模型对得上吗?

Robert C. Martin|24:03

对,当然对得上。这些模型有个挺有名的效应。而且这种事儿在不写程序的人身上也会发生,对吧?比如你在跟一个模型聊——哦,我不知道——聊什么咖啡最好喝、怎么冲才好喝之类的。你跟这个 agent 或者说这个模型聊得挺开心。然后有个人路过,正好在聊他最近在电视上看的肥皂剧。结果这些话就进了上下文窗口。不是你放进去的,是那个路过的家伙放进去的。但从那一刻起,所有关于咖啡的内容都得跟肥皂剧扯上关系了,对吧?模型分不清,它没有辨别能力。所以你这个"轨迹"的说法挺好的,对吧?只要你能让模型的方向保持不混乱——它知道自己要往哪走,上下文窗口里的一切都保持一致——那它就不会出现人们经常碰到的那种疯狂幻觉。甚至说跑偏(misalignment)也一样,对吧?我觉得幻觉始终是个危险,

Matt Pocock|25:12

对吧?不管是——你知道——幻觉,还是你喂给它的上下文,反正都一样。这算是一个大家公认的风险了。而你这套方法——让一串不同的 agent 组成"夹道鞭打"(gauntlet),把那些可怕的东西狠揍一顿——算是一种很好的消除它的办法。那么,好,实现阶段你已经讲了很多了,这个我们待会儿也可以再回来聊。我还感兴趣的是:你前期会做哪些规划?尤其是,你怎么考虑代码库的内部结构?因为有一套好的测试套件,对吧?我的意思是,我很想听听你的看法。有好的测试套件是好的,对吧?有变异测试,这些都很好。但如果你的 API 设计得很烂,模块形状很糟糕,那这些跟这一整套自动化检查之间会怎么互相影响?比如说,你有多——

Matt Pocock|26:04

——重视这件事?那么到大概上个月为止,我在那件事上的做法是——

//架构与规划:先拷问智能体,再动手(26:12–29:55)

Robert C. Martin|26:12

——那部分是手工做的。我会让 agent 给我搭出一个像样的东西,然后我会去"审问"这些 agent。这一步是手工的,但还是通过 agent 来做,对吧?我会审问它们:这里的结构是什么?这个模块跟那个模块是什么关系?到底有哪些模块?它们之间怎么通信?我会问这些问题。然后我会被吓得半死,因为答案实在太吓人了。然后我会——然后我会自己设计一个模块结构。我会告诉 agent:好,模块真正应该这样划分,你们应该这样跟它们通信。然后我会给它们一份实现计划,让它们去实现。这个嘛——这个挺难搞的。所以我还让我的 agent 给我做了一个架构查看器(architecture viewer)。这样我就能在屏幕上弹出一张漂亮的小 UML 图——本质上就是一张小 UML 图——向我展示系统的模块结构以及依赖关系往哪边走。

我可以点开一个模块,看到里面的子模块;再点开子模块,它还真能把代码弹到屏幕上给我看。所以我想钻多深就钻多深,可以在任何层级上查看系统架构。这对我特别有用,我把它用得很充分。我还另外搭了一个确定性的工具:我可以在里面定义哪个模块应该依赖哪个、哪个不应该依赖哪个,以及依赖应该怎么流动。这些会写进一个小巧而严格的规约文件,agent 不许违反。最后还有一个小检查器在末尾跑。如果它们违反了,就得想办法修——通常是反转一个依赖、插入一个接口,或者把一个模块劈成两半之类的。这样就能保证我的规则不被破坏。我现在正在研究能不能把这件事自动化。

到目前为止,进展不太顺。我基本上跟你处在同一条船上——

Robert C. Martin|28:20

——就是:只要模块设计得好,你能获得的杠杆实在太大了,对吧?你能解释一下这种——

Matt Pocock|28:27

——杠杆到底是什么吗?为什么这些模块有一个好的结构很重要?嗯,这还是那个——

Robert C. Martin|28:34

——跟我们当年说脏代码对干净代码(clean code)的那个论据是同一个。任何东西,只要划分得好、彼此之间接口有规矩,人类就能把握得住——因为我们的大脑本来就是分区思考的。模型也一样,agent 也一样。也许阈值稍有不同——这个我还说不准。但如果它们能聚焦在一个模块上,而且这个模块有一条——用你那个词——"轨迹",模型就不会被模块里的话题搞糊涂,那它们干活就会好得多。好吧,我这儿又是 model(模型)又是 module(模块)的——我们懂,我们懂。但这一点很重要。而且论据是同一个,就是咖啡和肥皂剧的那个论据。如果你把一个模块塞满天底下所有杂七杂八的东西,可怜的 agent 就会犯嘀咕:我到底在这儿干嘛呢?我在这里面还怎么干活?如果你把它分区分得漂亮,效果就相当不错——跟人一模一样。

Matt Pocock|29:40

所以你能从中得到,而且你可能也从测试套件里获得了更大的价值,因为我一直觉得就是这样。好吧,那我再问你一个相关的问题。我觉得,我得说我是你作品的铁杆粉丝,铁杆粉丝,但我同时也是——

//Ousterhout 与深模块(29:55–31:14)

Matt Pocock|29:55

——John Ousterhout 作品的铁杆粉丝。对。嗯,我是说,谁不是呢?他很了不起。

你手头说不定就有他那本书。他提出的 deep modules(深模块)这个概念,我觉得特别有意思:你可以有糟糕的模块,也就是那种浅模块,对吧,接口很宽,里面却没藏多少东西。你也可以有深模块,接口很小,但内部很深,藏着大量信息。我突然想到,这一点对模型来说特别好,因为它们只要读接口就行了,不必去理解实现。你觉得这个说到点子上了吗?你自己是不是也这么——

Robert C. Martin|30:31

——处理事情的?对。绝对是。模型会关注接口名。

名字,它们会关注结构。这让它们可以不用读底下的代码,这既是风险,也是优势。只要代码本身是一致的,就没问题。它们还会关注测试。它们会读测试来理解系统是干什么的。所以,任何能让代码结构变好的做法,都会帮助模型理解这些代码。顺便说一句,在这本书的附录里,有我和 John Ousterhout 之间一场很长的辩论。特别有意思。我俩都聊嗨了。好吧,我不知道他有多开心,但我是真聊得挺开心的。

//复杂度阈值、CRAP 分数与 TDD(31:15–36:10)

Matt Pocock|31:15

那场我全程都看了。我看到你们在一个很棒的 YouTube 对谈节目里聊到了这个,我真的特别喜欢。说实话,这也是我想请你上节目的原因之一,因为那场对谈我太享受了。好。现在我有好几个方向可以接着问。好。你的书里有没有什么内容,是你现在想要改一改、更新一下的?因为我具体想到的是一些建议,比如说要写小函数,保持函数短小。有没有什么东西是你有点想……因为我们一直在聊很多东西其实没变,对吧?像 gauntlet(关卡流程)这样的东西一直都是好的,只是我们从来没有足够的人力去真正把它贯彻到底,对吧?这些理念早就很好了,我们现在只是换了一种方式去调整它们。但有没有什么东西是我们真的需要……我想这个问题挺宽泛的。有没有什么东西是我们该彻底扔掉、已经过时的?或者有没有什么东西是……我其实不太喜欢问这种问题,因为它给你很大压力,但这个问题你有答案吗?

Robert C. Martin|32:25

嗯,阈值算一个。在我看来,agent(编程代理)能处理的复杂度级别跟人类不一样。它们的短期记忆好得多。我是说,超大的短期记忆,而且完全准确的短期记忆。所以我做的其中一件事,就是把函数允许的规模放宽。我通过调整 CRAP score(复杂度与覆盖率指标)来做到这一点。对人类来说,我会把 CRAP 数值控制在四以下,对吧?但对 agent,我设的是六,而且我在想,也许我会把它推到八。我在试着找到那个阈值在哪里,而这个阈值并不好找。

Matt Pocock|33:05

那从四到八具体意味着什么?比如说,四到十二?那中间——

Matt Pocock|33:09

——区别是什么?是像二十行到九百行的函数那种差别吗?所以归根结底还是圈复杂度——

Robert C. Martin|33:15

——(cyclomatic complexity),也就是穿过函数的路径数量。如果你的覆盖率是 100%,那么 CRAP score 为六就意味着这个函数里有六条路径,而且每条都有测试覆盖。所以这才是 CRAP 的真正目标,对吧?让测试全覆盖,然后限制圈复杂度。还有,你知道的,我跟 agent 辩论过好几次。顺便说一句,你跟 agent 的任何辩论都不能当真,但我反正还是照辩不误。它们似乎觉得,哦对,六大概挺不错的。好吧,你知道的,我不信它们,但行吧。但我觉得这里确实存在一个阈值上的差异。还有另外一个因素。在我的书里,我谈的是纪律(disciplines)。其中一条就是 TDD(测试驱动开发)。我是 TDD 的大力倡导者。但那是一种人类纪律。之所以这么做,是因为人类的构造方式就是那样。

我不能、也不会把这套强加给 agent。我觉得让 agent 先写一行测试、再写一行生产代码、然后再写下一行测试,毫无意义。我觉得这没有道理。对人类来说有道理,至少对我来说是。

这对我的工作有巨大的好处。但对 agent,我不这么认为。所以我允许 agent 表现得更像 John Ousterhout 主张的那样:先写一个函数,然后为这个函数写测试。再写下一个函数,再为那个函数写测试。我允许它们这么做。即使我明确要求它们严格按纪律做 TDD,它们最后总是会退回那种做法。它们最终总会变成那样。所以我就想,那大概也没问题。所以归根结底,把人类的纪律强加给 agent 可能是个错误。把人类的价值观强加给 agent 并不是错误,但有些阈值可能需要我们调整。不过这些纪律——

Matt Pocock|35:18

——本身、这些行为,我觉得把它们强加给 agent 并不明智。这个说法真漂亮。我很喜欢。所以关键在于短期记忆。我很喜欢这个说法。所以这是个短期记忆的问题,对吧?因为当你的短期记忆很弱的时候,TDD 特别好,人类就是这样,对吧?你的短期记忆刚好够你写完测试,然后又刚好够让测试通过,对吧?你还能溜出去喝杯咖啡,不是吗?好,太棒了。谢谢。那……好,我们聊了很多关于"把事情做对"的话题,对吧?把事情做对、做好实现,还有模块架构之类的。那在你把事情交给 specifier(规格生成器)之前,你会做些什么?在真正进入这个 gauntlet 循环之前,你会做多少规划?因为如果把错误的——

//对规格驱动开发的怀疑(36:10–44:08)

Matt Pocock|36:09

——工作塞进 gauntlet 里会特别浪费,对吧?这个问题你怎么看?嗯,所以说,那种诱惑——

Robert C. Martin|36:18

就是让,你知道,让人类去写规格、写规格、写规格、写规格,然后交给 agent?这是一个非常古老的诱惑。我们在 70 年代就经受过这个诱惑。它把我们带向了瀑布流程(waterfall)那一整套东西。而 Agile(敏捷)革命就是对那个的回应,或者说回击。我也不知道 Agile 革命现在做得有多好,但它当时是在说:等一下,这么重的前期规划(upfront planning)把一切都搞砸了,因为最终出来的东西跟计划一点都不像,从来都没像过。而现在面对 agents,诱惑就是干同样的事,对吧?好,我们要规划、规划、规划、规划、规划,然后交给 agent。我试过这个。事实上,就这周我还在试。

而结果,总是一场灾难。而且每次都是同一个结局,对吧?你把这些计划全都写出来。然后当 agents 跑起来的时候,你这个人,会意识到它们根本没法照着那个计划走。因为你不可能把一切都想到,而它们又不像你那么有智慧。所以它们就半吊子地朝着某个莫名其妙的方向狂奔,你得叫停、倒退、重写计划,然后再让它们重新跑。所以我已经放弃这条路了。我说,好吧,等一下。现在来试试 Agile 的做法。我不知道这么干效果会怎么样。可能效果也不怎么样。但咱们试试 Agile 的做法。就让它们先做一两个 story(用户故事)。然后到最后再看看架构,也许我得手动介入、整理几样东西,然后再做几个 story,以此类推。那可能是更好的做法。也许我们永远逃不掉最后那个手动整理的步骤。虽然我在想办法解决它。但我不知道这事办不办得到。

我的理解是,你手上有一大坨必须完成的劳动。

Matt Pocock|38:23

对。在过去,这一大坨劳动就是"把东西建出来",对吧?而把东西建出来可能要花几天、几周、几个月。现在这部分缩水了,对吧?但前期的规划和后面的 review(评审)还是老样子,对吧?我们作为开发者被期望做更快的迭代循环。但真正难的那部分,基本上还是一样的,还是要花那么多时间。把东西建错了,或者说错了,把错误的东西建出来,这种事你照样干得出来。而且我完全同意你说的,现在有这么一种,很多人都在搞这种"计划最大化"(plan maxing)的玩法,就是,你知道,他们拿着自己的 spec(规格),如果他们琢磨这个 spec,就把这个 spec 丢给七个不同的 agent 跑一遍之类的,然后他们,你知道,拿回一个更好的计划,然后照着去实施之类的。怎么会,就像,

Matt Pocock|39:11

这听起来不好吗?这些 agent 可太爱写计划了。我的天,它们可爱干这个了。而且它们

Robert C. Martin|39:17

还会把计划润色得天花乱坠。计划会写得华丽又漂亮。把各种各样的细节都给你列出来。

Matt Pocock|39:23

对,然后到最后就全盘崩了。

Robert C. Martin|39:29

所以,对,我想你现在在行业里就能看到。有一股朝着 spec-driven development(规格驱动开发)走的运动。而我的印象是,那玩意儿大概行不通。我自己的实验结果就不怎么好。我现在在想,也许我们应该再一次回头看看 Agile 的那些想法。也许就是那个想法:做一点,拿到点反馈,再做一点,再拿点反馈,重组一下,再多点反馈再重组一下。我以前讲 Agile 课程的时候常讲这个故事。假如说,对一栋房子做任何改动都只花你一美元,包括最开始打地基、最开始铺屋顶,所有的一切,你让承包商做的每个改动都只要一美元。你会怎么建这栋房子?你会雇一个建筑师,付给他几千美元,让他搞出一个完美的方案,然后再付一美元给承包商,让他一把建完?还是说,你会走到承包商跟前说,我要地基打在这儿。打成那个形状。哦,不行,那样很糟。

好吧,咱们改一下那个地基。咱们做一小步。把厨房放这边。把客厅放那边。这是两美元。哦,见鬼,不行,咱们把这两个换一下。让孩子们走走看,哦,动线不行,推倒重来。把楼梯挪个位置。显然,后者大概更好。而这就是我们眼下面对的局面。改动只要一美元。好吧,也许两美元,也许五美元。但改动的成本已经暴跌到了我认为我们这辈子能见到的最接近零的程度。而且,嗯,这个预言我多半会赌输,但话说回来,对吧?改动的成本已经低到这种地步了,你干嘛还要搞那种前期规划呢?因为那很贵。你干嘛不就这么调一调、调一调、调一调,

Matt Pocock|41:34

调一调、调一调、调一调,直到它看起来对为止。对。我百分之百同意。

我太同意了。我有个问题,我觉得我对 spec-driven development 这个标签(label)意见很大。因为 spec-driven development 到底是什么呢,对吧?就像,你可以有,比方说,10 种不同的解释。而且你每次给 agent 传递信息,对吧,prompt engineering 算 spec-driven development 吗?对吧?就像,某种意义上算吧,你知道,你不会,你知道,你会对你的,你知道,搁以前,你会有,比方说,坐在你对面的哥们儿,你的同事,你会跟他说,你就把 header 里那个加载问题修一下之类的。这算 spec-driven development 吗,对吧?就像,这算是我给他的一份规格吗,你知道?

而且好像每次我说,好吧,先做一点前期的对齐(upfront alignment),这种做法就会被叫成 spec-driven development。但对我来说,我觉得区别在于,你有没有把你的规格持久化(persist)下来,对吧?你会不会回头去翻那些规格?所以你在这事儿上的态度是什么?比如,你会不会在……里维护一个你所有 spec 的清单,

Matt Pocock|42:35

在 repo(代码仓库)里?还是说,你是,那边到底是个什么情况?(Bob 答:)不,我不会。规格是转瞬即逝的(ephemeral)。它们会

Robert C. Martin|42:42

消失。它们变得很快。我瞎鼓捣它们,然后,你知道,那玩意儿就没了。现在已经没有跟源代码等价的东西了。你知道,以前源代码是我们人类亲手写的。

所以那才是最终的规格。嗯,那个东西不复存在了。源代码还是有的,但写它的不是我们人类了。而很多人都感受到了这种缺失。好像必须有一个属于人的东西,在一开始就把一切定义清楚。嗯,我是说,说到底,就算是 agents 产出的东西,追根溯源也是人类产出的。我最近在做的事情是这样的。我不再去创建一份规格来定义我想要什么,甚至也不定义我手上有什么,而是看着最终结果说,嗯,这就是规格。所以我在外面放了一堆工具。比如那个 CRAP 工具,对吧,有 Clojure 版的,有 Java 版的。还有 Go 版的。其中几个是我自己写的。

有几个是我让 agents 写的。我还有个 mutation tester(变异测试器)。我有我的 agent harness(agent 脚手架)。这些东西我都挂在那儿了。我跟人们说的是,别去下载那些。那是我为我自己写的。你应该做的是,把你的 agents 指过去,让它们看看那些东西,然后给你自己造一个。我认为这是指定某样东西的本质、

//智能体什么都读,人类什么都不读(44:08–46:43)

Robert C. Martin|44:07

然后再把它定制成符合你特定需求的,一种好得多的方式。没错。这也是我一直发现

Matt Pocock|44:16

关于 agent(AI 智能体)有个很奇怪的点:你给它发个东西,它真的会读,对吧?这跟人类很不一样,对吧?你知道的,你要是给一个人发一份巨长的规格说明书,命中率可能也就 20%。20% 可能还说高了,也许 5%。但如果你把一份 spec 交给 agent,它多半是会读完的。对吧。

Robert C. Martin|44:36

而这事的另一面是:agent 写出来的东西,人类不去读。

Matt Pocock|44:41

对,没错。对,就是这样。所以它们是指望……我倒是这么想过。它们指望我们把它们写的东西全都读了,而我们就是不读。天呐,这太好笑了。这也太单方面了,你知道吧。

长远来看,我很好奇人际关系会怎么变,或者说变得不一样,因为我们已经太习惯和 agent 交流了,对吧?我是用口述对我的 agent 下指令的,所以感觉就像是在跟一个朋友聊天之类的。我真正感兴趣的是,随着时间推移,生活如何模仿艺术之类的。我也不知道。这对我来说很迷人。

好了,那我想我要问的可能是最后一个问题了,是个挺有分量的问题。可以吗。对。

算是最后一个吧。我要再说回 John Ousterhout,因为 John 对不同类型的编程有个很棒的定义,对吧?有 tactical programming(战术式编程)。你要去拿那本书吗?

Robert C. Martin|45:33

我非拿不可。你都提到了。我必须得拿。

我得把它抽出来。

Matt Pocock|45:36

他的书?应该就在这附近。我的拿到了。哦,好。

好,行。好了。我的拿到了。

对。他们也拿到了。我们都拿到了。

耶。好。搞定。

tactical(战术)对 strategic(战略)编程。战术就是在一线的中士,是在打仗的那个人。战略那块是将军,是指挥战争走向的人。我想我们都同意,对于不同类型的编程,这是个很好的框架,对吧?而 agent 特别擅长战术,战略上特别不行。那么,对于刚入行的人来说,AI 现在已经把战术那部分全吃掉了,他们要怎么学会战略式编程呢?因为我觉得在座很多人,他们想从你这里得到的,Bob,差不多就是这个。他们想要的是:拜托把你的大脑给我,让我能注入进去,搞明白为什么 mutation testing(变异测试)这么好。告诉我怎么解释这些模块。我觉得他们要的就是这个。所以你现在的任务就是把你的大脑给大家。他们当年是怎么学会这些东西的?

//新手如何学:先写代码,再看智能体挣扎(46:43–54:34)

Robert C. Martin|46:42

好。这个问题我经常被问到。我没有完美的答案,因为我真的不知道。

但我会这么来思考这个问题。我确实是这么想的。首先,一个程序员应该怎么学,不管是在大学里还是在别的地方,对吧?应该是去写代码。你应该写上一年的代码。我不知道具体多久,但你应该写代码,这样你才知道 agent 在面对的是什么。接下来我认为应该发生的是:当你被一家大量使用 agent 的公司录用时,你,一个刚完成培训的年轻人,应该被当作一个 agent 来对待。那个管事的,你知道,可能是 lead engineer(主管工程师)之类的,那个管着一群 agent 在跑、自己做战略决策的人,他应该把你当成一个 agent 来看。他应该给你派和 agent 一样的任务,让你用 agent 必须用的那套确定性工具(deterministic tools)。你应该在这种状态里待上几个月,产出低得可怕,但学到的东西多得吓人。

等你闯完这一关,也许就可以被信任。去自己带一个 agent 了。我也不知道。大概会是这样一条路。你不能把代码完全丢掉。我十年前常跟人说,我常跟人说,如果你从没写过汇编语言,你应该花一个周末写写汇编,就为了知道幕后到底在发生什么。因为如果你整天只是写 Java,你是活在一个幻想世界里。那里头还有魔法。你不明白。花一个周末写汇编,你最终会明白到底发生了什么。而我觉得这一点现在依然成立。在这条教育路径上,你总得从最基础的二进制开始,一路走过汇编语言,写点 C 这样的基础代码,再写点 Python 之类的高级语言,然后……再去处理 agent 这类工作和确定性工具,最终才能有战略地驾驭一个 agent

Robert C. Martin|49:11

在监督之下。

Matt Pocock|49:14

对。我也不知道怎么说得更好了。但这太难了,对吧?因为这中间有两件事在发生,对吧?一个是,agent 是代码之上的一层抽象。所以你有了代码,然后就很有意思了。你之前说的一种抽象层,像是一种模块查看器(module viewer),对吧?我觉得那个超级有意思。那是个很好的学习方式。在代码之上有了这类抽象,等你深入进去的时候,就能理解得更深。但是,如果你雇了一个人,他就挂在你的工资单上,只做战术性的活,那作为一家公司,你肯定会看着这情况想:我们为什么要雇这个人?我们已经有,你知道的,我们有 Uncle Bob 的那一群五个什么什么的东西,能打得过他,而且成本只是零头,对吧?

我就是,我,我太……好吧,那么,有什么,比如资源或者书,或者方法,或者,抱歉,这里还有一个问题,就是我想搞清楚人们是怎么获得这些认知的,因为战略式编程的反馈循环太长了,对吧?或者说传统上一直很长。所以经常会出现这种情况:有些人干了六个月就跳槽了,他们可能永远学不会战略式编程,对吧?因为他们犯的错可能要九个月后才显现,你知道,所以他们永远看不到自己的错误。但我觉得有了 agent,就可以了,对吧?因为速度加快了这么多,你确实能更早得到关于自己错误的反馈。而我感兴趣的是,去年 12 月你看着你的那些 agent 说"这是一坨狗屎"的时候,你是怎么知道它们在犯错的?你是怎么识别出来的?

Robert C. Martin|51:00

早期我就是看代码,看出那是一坨狗屎。那不是重点,重点是下一步:我看着它们在那里空转挣扎。我能看出 agent 在挣扎,而我能认出那种挣扎,是因为我自己也经历过那种挣扎,对吧?问题之一就在于,新手进来是认不出这种挣扎的。那这东西怎么学呢?怎么学呢?你知道,我是吃了很多苦头才学会的——老派的摸爬滚打。怎么学呢,你作为一个刚入行的年轻人,怎么意识到关于这个有一大堆知识,呃,那些老书,那些因为太老而没人读的书,呃,这个话题的老书写得非常好,对吧?你去看 Tom DeMarco 的著作,或或或 Ed Yourdon 的著作,或者你知道,去读读,你知道,呃——她是——我这会儿想不起名字了,但我,我说的是 Pragmatic Programmer 吗?

对,《The Pragmatic Programmer》(《程序员修炼之道》)。呃,有很多,有很多,有很多这样的老书,都非常好,呃,如果你,如果你趁年轻研读它们,你就会对这种更高层次的战略玩法有一种感觉。你得过滤掉一些过时的东西,因为很多这类书写于 70 或 80 年代,对吧?但这些教训正是在那个年代学到的。所以我,你知道,我我我一开始会去那里。我会去读那些书,用那种方式学会这些东西。然后当然,你还得靠亲身感受去学。对,这就是为什么我觉得他们应该扮演几个月的 agent,这样我才能学到那到底是什么感觉——成为那个 agent。让 agent 把任务委派给你。对,

Matt Pocock|52:49

你就成了 agent 的子 agent。我喜欢这个说法。好,那我确实还有一个问题。应该是个简短的问题,就是:这么听下来,软件基本功(software fundamentals)依然重要,对吧?为什么会这样?对那些说基本功不重要的人,你会说什么?为什么会这样?

Robert C. Martin|53:15

为什么这么说?嗯,软件基本功之所以重要,原因就是它一直以来重要的那个原因。这个——让我想想,这话是谁说的来着?是,嗯,嗯,呃呃,Dijkstra,我想是他说的。我可能会记错,不过他说过:软件是人类有史以来尝试过的最复杂的东西,比我们做过的任何其他事情都复杂。软件是最复杂的东西,因此基本功就是把这种复杂性组织起来的方法,把它组织成一种不仅能被人理解、也能被我们的模型(指 AI 模型)理解的形式——毕竟我们的模型本来就是照着人来建模的。所以基本功依然适用,因为这就是我们组织复杂性、让它变得可以被理解的方式。现在有些人觉得基本功不重要了。他们会明白过来的,而且会是以惨痛的方式明白过来。而且,而且这不会花太长时间——可能比我以为的要久一点,因为现在的 agent(智能体)确实挺厉害的。但我亲眼见过它们撞上那堵墙。所以我知道那堵墙就在那儿,我可不想再撞一次了。

//尾声:抽象层之上,回到浴袍(54:36–56:39)

Robert C. Martin|54:35

嗯,非常感谢。我们此刻所处的局面,有一系列非常有意思的历史对照。我们——你刚才提到了抽象层,对吧?你知道,我们现在已经站在了编译器之上一层。以前——我们的抽象层曾经是编译器,再往前是汇编语言,再往前是二进制,而现在已经升到了模型这一层。每往抽象层上走一步,站在低一层的人就会抱怨,说:哦,这会把一切都毁掉的。我们都要没工作了。这东西简单到五岁小孩都能写代码了。你知道,当年他们说的还是二进制呢。每一步都是同样的剧本。所以现在我们走到了下一个台阶,下面这层的人又在说:哦,他们要毁掉一切了。不,不会——还是同样的规则在起作用。所有同样的基本功依然存在,原因也跟以前一模一样。你现在扔掉的那些规则,一年之后你会从地板上把它们捡起来、掸掉灰尘,然后……

Matt Pocock|55:45

……重新想起自己为什么需要它——是的,非常感谢你,Bob。有句话说得好,好像是柏拉图说的,他说文字的发明会让人类变蠢,对吧?所以说,同样的

Matt Pocock|56:00

关于抽象的争论,从古希腊那会儿就有了,对吧?——太离谱了。——我想大家都在看——我们这场直播现在有多少人?呃,有 1500 人在看。不可思议。呃,我想大家都会同意,这是一场非常精彩的对话。嗯,Bob,太感谢你了。呃,再把你的书举起来一下,让大家,呃,能看清楚。让我看看。

Matt Pocock|56:25

《Clean Code》(《代码整洁之道》)。还有——你那边现在几点,上午 11 点?又要换回浴袍了?——呃,对对。嗯,没错,完美。好了各位,非常感谢大家。

直播就到这里。嗯,我和 Bob 会,呃,再私下待一会儿。


//对谈里的 Mindset

以下是基于节目原话提炼出的思维方式,不是 Bob 逐字说出的口号。每一条都保留了它形成的经历、适用条件或不确定性。

1. 快不是目的,干净才是——因为不干净会反过来吃掉快

Bob 最初被 agent 的速度吸引,但不清理代码就继续派活,agent 会越来越慢,最终进入“改一处坏一处”的空转。速度只有在技术债不持续吞噬它时才有价值(10:47)。

2. 把人的慢从执行回路里拿掉,把人的判断放到回路外

他的目标不是盲目信任 agent,而是用 CRAP 分数、变异测试、抽查和架构约束代替逐行人审。需要注意:这是他正在努力逼近的目标,不是已经完全实现的状态(04:19)。

3. 能外置的约束,就不要依赖模型记住

Prompt 里的规则会随上下文变长而变软;检查器、测试和依赖规则不会。对 agent 最有效的约束,往往不是“再强调一次”,而是把要求变成不能绕过的通过条件(13:44、16:21)。

4. 不要被模型的赞同欺骗

Bob 会与 agent 讨论 CRAP 阈值,但他明确提醒:“你不能相信和 agent 的任何辩论。”模型的认同不能替代实验、测量和失败边界(33:15)。

5. 价值可以通用,纪律要按主体重估

测试、覆盖率、低复杂度和清晰结构是价值;严格 TDD 的微小步节奏,是针对人类短期记忆设计的纪律。主体从人换成 agent 后,前者仍需坚持,后者应该重新验证(33:15)。

6. 规格可以是易耗品,但“为什么这样设计”不能凭空消失

Bob 不再长期保存任务规格,而把最终产物视为规格。这提高了迭代自由,也留下一个他没有回答的问题:系统的设计理由应该存在哪里(42:42–44:36)?

7. 老手认得的不是“丑”,而是“挣扎”

Bob 判断 agent 出问题,关键不是静态地看出代码难看,而是看见它反复空转,并认出自己曾经历过的挣扎。战略判断来自反馈历史,不只是规则记忆(51:00)。

8. 抽象层每抬高一次,人们都会误以为基本功失效

二进制、汇编、编译器和模型,每一次抽象升级都伴随着“工作要消失、旧规则没用了”的判断。Bob 的结论是:工具层会变化,组织复杂性的理由没有变化(54:35)。

//术语速查

  • >
    • agent(智能体):不仅生成文本,还能读取代码、调用工具、执行任务并根据结果继续迭代的模型系统。本期语境中主要指 coding agent。
  • >
    • deterministic tools(确定性工具):对同一输入给出可重复、明确结果的检查器,例如测试、lint、复杂度分析和依赖规则检查(13:44、16:21)。
  • >
    • CRAP score:Change Risk Anti-Patterns,把测试覆盖率与圈复杂度组合起来衡量函数风险。本期 Bob 用它限制 agent 生成代码的复杂度(04:19、33:15)。
  • >
    • cyclomatic complexity(圈复杂度):函数中独立执行路径的数量。在 100% 测试覆盖时,Bob 把 CRAP 分数近似理解为路径数量(33:15)。
  • >
    • mutation testing(变异测试):主动修改代码中的运算符或条件,检查测试是否会失败;若测试仍通过,说明测试套件可能存在漏洞(04:19)。
  • >
    • lost in the middle(迷失在中间):长上下文中,中部信息更容易被模型忽略的现象。Bob 用它解释为什么长 prompt 中的早期规则会逐渐失效,同时承认自己只做了有限研究(13:44)。
  • >
    • Gherkin:用 Given–When–Then 描述可验收行为的格式。Bob 的 specifier agent 会把人写的需求转成 Gherkin 测试与 QA 流程(19:17)。
  • >
    • deep module(深模块):John Ousterhout 提出的设计概念,以较小、稳定的接口封装较多实现能力。本期两人用它解释为什么清晰边界也能帮助模型理解系统(29:55–30:31)。

//金句

$

“They are fast with code. I am slow with code. So I'm going to let them have the code. And I'm going to deal with the stuff around that to make sure it's all okay.”

它们写代码很快,我写代码很慢。所以我会把代码交给它们,而我来处理代码周围的东西,确保一切正常。

—— Robert C. Martin,04:19

$

“…these agents, they may be fast and maybe relatively smart, but they are as subject as humans are to messy code. Now, maybe not as subject. Maybe there's a difference in threshold. But the threshold is still there.”

这些 agent 也许很快、也相对聪明,但它们和人一样受烂代码影响。也许没那么严重,也许阈值不同——但那条阈值仍然存在。

—— Robert C. Martin,10:47

$

“So deterministic tools don't disappear that way. The key that I think with agents, and this is really hard to do. The key with agents is to trim that initial prompt down to its absolute minimum”

确定性工具不会以那种方式消失。我认为使用 agent 的关键——这件事真的很难——是把初始 prompt 裁剪到绝对最小。

—— Robert C. Martin,13:44

$

“They also pay attention to the tests. They read tests to understand what the system does. So, anything you can do that helps the structure of the code will help the models understand that code.”

它们也读测试,通过测试理解系统做什么。所以,你为代码结构做的任何事,都会帮助模型理解那份代码。

—— Robert C. Martin,30:31

$

“So the bottom line there is it's probably a mistake to impose a human discipline on an agent. It is not a mistake to impose human values on the agent, but there may be thresholds that we need to change.”

把人类的纪律强加给 agent 可能是个错误;把人类的价值强加给 agent 不是错误——但有些阈值可能需要调整。

—— Robert C. Martin,33:15

$

“And by the way, you can't trust any debate you have with an agent, but I still have them anyway.”

你不能相信你和 agent 的任何辩论——不过我还是会照样跟它们辩。

—— Robert C. Martin,33:15

$

“There's this movement towards spec-driven development. And my impression there is that that's probably not going to work. My experiments did not work particularly well.”

业界现在有一股规格驱动开发的潮流。我的印象是它大概率行不通——我自己的实验结果就不怎么样。

—— Robert C. Martin,39:29

$

“The opposite side of that is the things that the agents write, the humans don't read.”

另一面是:agent 写的东西,人类不读。

—— Robert C. Martin,44:36

$

“I could see the agents struggle And I recognized the struggle since I have been through that struggle, right?”

我能看见 agent 在挣扎,也能认出那种挣扎——因为我自己经历过。

—— Robert C. Martin,51:00

$

“The rules you throw away are the ones you're going to pick up off the floor in a year and dust off…”

你今天扔掉的规则,一年后会从地板上把它们捡起来、掸掉灰……

—— Robert C. Martin,54:35(句尾被主持人的致谢打断)

//选择题

这些题只检查机制、决策边界和工作流,不考人名、书名或孤立数字。先作答,再看后面的解析。

1. 为什么 Bob 更愿意把关键约束写成确定性检查,而不是继续加长初始 prompt?

A. 确定性工具生成代码更快B. 长上下文中的规则可能被稀释或忽略,而检查器能持续给出明确的通过/失败结果C. Agent 完全无法理解自然语言规则D. 所有工程规则都能自动化

2. 在 Bob 的多智能体流水线里,cleaner 和 hardener 的主要区别是什么?

A. cleaner 写需求,hardener 负责上线B. cleaner 只检查界面,hardener 只检查数据库C. cleaner 主要做 CRAP 分析与一般代码清理,hardener 用变异测试等手段检验测试是否足够强D. 两者没有实质区别,只是为了并行而复制

3. “把人类价值强加给 agent,但不要照搬人类纪律”最准确的理解是?

A. Agent 不需要测试,只需要更长的上下文B. 所有团队流程都应该立刻取消C. 可验证、低复杂度、结构清晰等目标仍要坚持,但为人类认知局限设计的具体做法与阈值需要重新实验D. 只要 agent 速度够快,代码质量可以放弃

4. 下列哪项最符合 Bob 对 spec-driven development 的实际立场?

A. 他已证明规格驱动对所有 AI 项目都无效B. 他自己的重前置规划实验效果不好;他猜测低变更成本更适合短周期迭代,但替代方案尚未验证C. 他主张完全不做架构,只让 agent 随机尝试D. 他认为规格必须永久保存在仓库,作为唯一事实来源

5. Bob 为什么认为新人需要先亲手写代码,并在一段时间里接受类似 agent 的任务与检查?

A. 因为新人比模型便宜B. 因为只有手写汇编才能成为架构师C. 因为公司需要用新人替代自动化测试D. 因为识别 agent 的空转与挣扎,需要先积累自己处理复杂性、犯错和得到反馈的经验

参考答案与解析

  1. 1. B。Bob 的机制解释是 lost in the middle:prompt 变长后,早期规则可能被挤到上下文中部而失去权重;确定性检查不会以这种方式“消失”,还能迫使 agent 循环修正直到通过(13:44、16:21)。D 说得过满:并非所有约束都能自动化。
  2. 2. C。cleaner 运行 CRAP 分析并做一般 code review;hardener 运行变异测试,寻找测试没有杀死的 surviving mutants。两者分别约束实现的复杂度与测试套件的检错能力(19:17)。
  3. 3. C。Bob 保留的是测试、覆盖、低复杂度等价值;他重估的是严格 TDD 的逐步节奏及 CRAP 阈值,因为这些纪律原本针对人类的短期记忆和认知限制(33:15)。
  4. 4. B。他的证据只来自个人实验,原话是 “My experiments did not work particularly well”;他提出的敏捷式替代也明确带有 “I don't know” 的限定,因此不能扩大成普遍定律(36:18、39:29)。
  5. 5. D。Bob 说自己能认出 agent 的挣扎,是因为自己经历过同一种挣扎;“先写代码、再当几个月人肉 agent”是他据此提出的培养猜想,但他也先声明自己没有完美答案(46:42、51:00)。

//思考题

以下问题是编辑基于对谈提出的延伸,不是 Bob 的原话或主张。

1. 你的工具箱里,有哪些“原理成立、人工成本太高”而被搁置的检查?

Bob 的真正方法不是照搬 CRAP 或变异测试,而是重新检查旧技术当年的否决理由。静态分析、契约测试、依赖约束、形式化方法的轻量版本里,有没有什么只是因为过去“跑不起、修不起”才被放弃?Agent 出现后,成本判断是否已经变化?

2. 你的约束有多少写在上下文里,有多少写在上下文外?

把团队的 AGENTS.md、CLAUDE.md 或提示词规则逐条过一遍:哪些可以变成测试、lint、类型约束、CI 检查或权限边界?哪些必须依赖语义判断,只能留在 prompt 里?如果只能留在 prompt 里,怎样缩短和排序才能减少失效?

3. 你所在团队的哪些流程属于“价值”,哪些只是“为人设计的纪律”?

测试是价值,严格 TDD 是一种纪律。把这个区分迁移到站会、结对、小步提交、代码审查和发布流程:当执行主体换成 agent、外包或新人时,哪些结果仍必须坚持,哪些行为可以重新设计?

4. 如果规格是易耗品、代码不再由人亲手写,“为什么这样设计”的知识应该放在哪里?

Bob 的架构查看器和依赖规则文件暗示了一种答案:把知识存成可执行约束和可视化结构。但这仍不足以解释历史决策、被否决的方案与业务权衡。你的团队需要保留哪些决策记录,才能避免下一次修改只看见“是什么”,却不知道“为什么”?

5. 当新人不再拥有大量低风险的战术练习,怎样设计新的反馈阶梯?

Bob 提出的“先写代码,再当几个月人肉 agent”只是猜想。你能否设计一个更可验证的版本:限定任务范围、保留真实失败、使用同一套确定性检查、由资深工程师复盘 agent 与新人的不同挣扎,并明确何时允许新人开始承担战略决策?

相关学习资料