article.md
v4F1gFy-hqg-20260518-202128-01

《"Software Fundamentals Matter More Than Ever" — Matt Pocock》

来源:YouTube [AI Engineer]
时长:00:18:26
视频ID:v4F1gFy-hqg
链接:https://www.youtube.com/watch?v=v4F1gFy-hqg
说明:以下文字按视频原讲述顺序整理,口语、重复和明显噪声已做轻度收束。

开篇:为什么基础知识仍然重要

我想给你们传递一个消息,希望这个消息能给那些觉得自己技能在这个新时代一文不值的人带来一点安慰——我认为,有自我意识的软件基础现在比以往任何时候都更重要。我是一名教师,最近正在讲授一门叫《面向真正工程师的Claude Code》的课程,名字故意带点挑衅。在准备这门课的过程中,我不得不设计一套关于AI编程的教学大纲,这真是噩梦般的事,因为一切都在不停变化,对吧?AI是一个全新的范式。我们当然要抛弃所有旧规则,才能引入新东西。围绕这一点,兴起了一场名为“规格到代码”的运动。它主张:你可以写一份关于应用程序应该如何工作的规格说明,然后利用AI把它转换成代码。如果应用出了问题,你就回到规格说明,根本不用看代码,直接改规格,再跑一次编译器,就会得到更多代码。听过这个说法的人请举手。试过的人请继续举手。好了,我也试过,可以把手放下了。我注意到,我跑程序时努力不去看代码,但还是会看。然后我发现,第一次跑拿到的代码还不错;再跑一次,代码变差了;第三次更差;我持续跑编译器,跑了一遍又一遍,最终得到的全是垃圾。这种事你们遇到过吗?遇到过请举手。没错,我觉得这办法行不通。认为可以忽略代码、让代码自己管理自己的想法,不过是另一种形式的“AI编程”而已。我之前并不这么认为。我当时想:怎么修复编译器?怎么让它每次不产生坏代码或更差的代码?于是我想到,我需要用英语向语言模型解释一个好的代码库长什么样。于是我想起一本我钟爱的老书——约翰·奥斯特豪特(John Ousterhout)的《软件设计哲学》。我上亚马逊买了它。书中给出了坏代码的定义,称之为“复杂代码”:复杂性指的是软件系统结构中任何让系统难以理解和修改的东西。也就是说,坏的代码库是难以改变的代码库——如果你修改它时总会引入缺陷,那就是坏代码;好的代码库则容易改变。我觉得这点很有道理,于是又找了一本书——《程序员修炼之道》(The Pragmatic Programmer),也上亚马逊买了它。书里有一整章讲“软件熵”,这恰恰就是我看到的现实。熵意味着事物倾向于走向混乱、分崩离析、最终崩溃。大多数软件系统也是这样的:每次你对代码库做修改,如果只考虑这次修改本身而不顾及整个系统的设计,代码库就会越来越差。而我在“规格到代码”中看到的正是这样——每次都只跑编译器,结果代码越来越糟。驱动“规格到代码”运动的一个理念是“代码很便宜”。听说过这个说法的人请举手。是的。我认为这个说法不对。代码并不便宜。事实上,坏代码的成本比以往任何时候都高。因为如果你的代码库难以修改,你就无法充分利用AI带来的全部红利。AI在好的代码库里确实表现非常好。这意味着好的代码库比以往更重要,也意味着软件基础比以往更重要——这就是本次演讲的核心论点。那么我们来谈谈实践吧。我会讲一些你可能已经遇到过、或者还没遇到过的AI相关的失败模式,以及如何通过回归那些经典书籍、遵循良好的软件实践来避免它们。这样行吗?第一个失败模式是:AI没有按我的想法做事。我明明脑子里有一个好主意,但AI却做了一件完全不同的事情,或者它产生的规格结果根本不是我要的。遭遇过这种情况的人请举手。很好。其实,正如《程序员修炼之道》所说,没有人确切知道自己想要什么。

失败模式一:AI不听话 – 共享设计概念

他提到,有时候你脑子里想得很清楚,但AI却做出了完全不一样的东西,或者生成了一些不符合预期的规格。他问现场有没有人遇到过这种情况,不少人举了手。他指出,这其实就和《程序员修炼之道》里说的一样:没有人能确切知道自己想要什么。而和AI沟通时存在一种天然的障碍,你向AI描述需求的过程,本质上就是AI在帮你做需求梳理,它要从你的描述里搞明白你到底需要什么。

失败模式二:AI话太多 – 通用语言

接着他想到了另一本书——弗雷德里克·P·布鲁克斯的《设计之道》,书中提到一个概念叫“设计理念”。当多个人一起设计某样东西时,大家心里对正在构建的事物会有一个模糊的、无形的想法,这就是设计理念。它不是一份文档,也不是一个可交付的资产,而是关于你要建造什么的、看不见的那套构想。他意识到,自己和AI之间缺乏的就是这个共享的设计理念。于是他发明了一个技巧,非常简单,就叫“拷问我”(grill me)。技能描述是这样的:“无情地就这个计划的每一个方面盘问我,直到我们达成共同理解;沿着设计树(这也是布鲁克斯提到的概念)的每一个分支走一遍,逐一解决决策之间的依赖关系。”这个技能对应的代码仓库收获了约1.3万星,爆火出圈。就这两行描述,会让AI提出40到60个问题,他有时甚至被问了上百个问题,直到AI觉得双方已达到共同理解。这样一来,AI就变成了一个“对手”,不断向你抛出想法,努力达成共识。随后你们生成的对话内容,可以用来整理成产品需求文档;如果是小改动,甚至可以直接转化成工单,交由后续的自主代理(AFK agent)处理。他特别强调,尽管可能有争议,但他个人认为这种做法比他常用的工具(Claude Code)里的默认“计划模式”更好。计划模式太急于生成一份资产,它只是想快速出一个计划然后开始干活;而他觉得先建立共同的设计理念会更舒服。这就是第一个建议。

失败模式三:AI代码不工作 – 反馈循环与TDD

第二个失败模式是AI说话过于啰嗦。它总是用太多词来解释自己在做什么,双方仿佛在用不同的语言沟通。他问现场有没有人也有这种感觉,很多人举手。这让他想起了自己多年做开发时的经验:和领域专家合作时,比如专家要你做一个微芯片相关的应用,而你完全不懂微芯片,那就必须建立某种共同语言。否则专家用的术语你不懂,你翻译成代码后可能连自己都不理解,领域专家更看不懂,这就产生了语言鸿沟。于是他回头去研究领域驱动设计(DDD),虽然还只是探索阶段,但读到的每一点内容都让他深受触动。DDD里有一个概念叫“通用语言”(ubiquitous language)。在这种语言下,开发者之间的对话、代码里的表达、以及与领域专家的交流,全都基于同一个领域模型。简单来说,就是一个Markdown文件,里面罗列了你和AI共用的术语。你们要聚焦这些术语,确保它们的含义一致,并且在代码、对话以及和AI交流时始终使用它们。他为此写了一个技能——通用语言技能。它会扫描你的代码库,识别其中的术语,然后生成一个Markdown文件,里面用表格列出了所有术语。他把这个文件传给AI,自己也会一直打开它,在和AI“拷问”和规划时参照。通过阅读AI的思考痕迹,他发现这不仅提高了规划质量,还让AI的思维变得更简洁,实际产出也更贴合你的计划。这个技巧非常强大,效果出奇地好。这就是第二个建议:和AI建立共享语言。

失败模式四:代码库不可测 – 深模块架构

好了,现在假设你和AI已经对齐了,你知道要建什么,AI也正确地把东西造出来了,但它就是不工作。他问现场有没有人遇到过这种情况,很多人举手。

技能:重构为深模块并提升可测试性

第二个技巧是:与AI建立共享语言。假设你已经和AI对齐了,知道要构建什么,AI也构建了正确的东西,但它就是运行不起来——这种情况你们遇到过吧?对,就是不行。要改善这一点,有一个很明显的做法就是利用反馈循环。比如使用静态类型,如果你不用TypeScript那就是疯了;如果你在构建前端应用却不给LLM访问浏览器的权限,让它能看看界面效果,那绝对需要这么做。当然你还需要自动化测试。但我注意到一个问题:即使有了这些反馈循环,LLM也并不会很好地利用它们。它没法像资深开发者那样充分利用反馈,而是倾向于一次性做太多事。它会产出大量代码,然后才想“哦,我应该再检查一下类型”或者“嗯,也许该跑个测试”。在《程序员修炼之道》中,这被称为“超出车头灯范围”,即开得太快,而反馈的速度就是你的速度限制。反馈的速度是你的速度限制,这意味着你应该边做边测试,采取小而谨慎的步骤。但AI默认并不擅长这个。所以第三个技能是TDD,也就是测试驱动开发。TDD迫使LLM真正迈出小步:先写测试,让测试通过,然后重构代码让它更漂亮并考虑设计。

失败模式五:开发者脑力过载 – 灰盒委托

但问题是测试本身很难,一直很难。因为写测试时你需要做大量决策:要测试多大的单元?要mock什么?首先应该测试哪些行为?所有这些决策都是相互依赖的。如果你测试一个非常大的单元,比如整个应用程序,那测试可能很不稳定,你也不希望测那么多行为。如果你只测试这个单元,就需要mock那个单元——一切都环环相扣。我整个开发生涯都在思考这个问题。我们发现好的代码库就是容易测试的代码库。这里又回到了代码重要性题:你的代码库越好,反馈循环就越好,因为你能够给LLM更好的反馈,它产出更好的代码。那么,什么是好的、可测试的代码库呢?我们再次求助于John Ousterhout。他谈到代码库中应该有深模块,而不是浅模块——不是很多暴露很多函数的模块,而应该相对较少的大型深模块,具有简单的接口。我们来快速比较一下:深模块将大量功能隐藏在简单接口之后,隐藏复杂性;你可以查看深模块内部,但没必要,直接用接口就行。浅模块功能不多,接口却很复杂。浅模块在代码库中就像一堆密密麻麻的小块,AI需要逐个遍历导航,这实际上很难探索。如果你有这样的代码库(AI特别擅长生成这样的代码),往往会出现AI不理解你的代码在做什么的情况。它会尝试探索代码,但由于布局糟糕、充满浅模块,它可能无法及时找到正确的模块,或者搞不清所有依赖,因此不理解你的代码。那么充满深模块的代码库是什么样呢?就像这样:相同的代码,但被组织在边界内,顶部有接口。这些接口你应该好好控制、精心设计,否则AI可能会搞乱设计;而实现部分则可以稍微交给AI。那么如何把一个像前者的代码库变成后者呢?我有一个技能:改进代码库架构。虽然做起来相当复杂,但有一套可重复使用的步骤:你只需要探索代码库,寻找代码相关的地方,把所有相关内容包裹进一个深模块。这样得到的代码库就是可测试的,因为这些代码的边界非常简单——你在接口处测试,通过该接口验证就足够了。

终章:战略设计每日投资

探索代码库,寻找那些相互关联的代码,然后把所有这一切封装在一个深层模块里。这样的代码库是可测试的,因为代码周围的边界非常简单。你在接口层面进行测试,通过那个接口验证,就行了。所以,这种代码库很适合测试驱动开发(TDD)。那么第六个失败因素呢?假设你的反馈循环已经正常运行,事情开始步入正轨,你能比以前交付更多的代码,但你的大脑却跟不上了,对吧?举手示意一下,有多少人觉得自己在开发生涯中比以往任何时候都更累?我也是,真是筋疲力尽。我认为这种代码库实际上会让你的大脑更难承受,因为你和AI都需要把所有信息记在脑子里。而另一种方式不仅让你更容易阅读和理解,还意味着你可以把这些深层模块当作灰盒来处理。你可以说,好吧,我只设计接口,而不去过多担心或审查内部实现。当然,这在应用里不太关键的部分是可以做到的,但像金融等场景就不行。不过,在你的应用中的许多模块里,只要你有一个可测试的外部边界,只要你知道它的用途并能从外部设计它,你就不必太考虑内部实现。我发现这真的拯救了我的大脑,因为我可以直接说:好,AI,你来处理那个大块头内部的实现,我只从外部测试和验证就行。所以这是第五个技巧:设计接口,委托实现。但这意味着每当我们接触代码、规划任务时,都需要思考并留意应用中的模块。我们需要非常清楚那张地图,它必须成为我们通用语言的一部分,也要融入我们的规划技能之中。所以,在我写的PRD(产品需求文档)里,我会具体说明模块变更以及模块内接口的修改方式,我一直在思考它们。这来自Kent Beck的理念:每天都要投资于系统设计。这正是核心所在,对吧?因为从规格到代码,我们并没有投资于系统设计,反而是在撤资、在放弃它。而我认为这一点至关重要。所以我想传达的信息是:代码并不廉价,代码很重要。如果我们把AI看作一个非常优秀的底层程序员——一个战术层面的程序员,一个在战壕里执行代码更改的士官——那么你需要在它之上,需要一个在战略层面思考的人,那就是你。而这需要我们已经使用了二十多年甚至更长时间的软件基础技能。如果你对我提到的这些技能感兴趣,可以在GitHub仓库(MacPocockSkills)里找到。如果你想了解我提供的培训或免费资源,我在YouTube和Twitter上都有账号,你也可以访问aihero.dev,那里有我的新闻通讯。非常感谢。希望这能让你在这个新的AI时代充满信心,相信你实际上可以产生积极的影响。谢谢。


本文由 AI 基于转写整理,仅供复查参考。