Simon Willison不是AI乐观派,他是工程纪律派
风格参考:左耳朵耗子(陈皓)—— 工程师本位、强工程纪律、爱拍桌子、举失败案例。立场:拥护。
先把话挑明
我想先把一句话挑明:Simon Willison不是AI乐观派,也不是AI悲观派,他是工程纪律派。
这句话听起来像是和稀泥,其实不是。今天市面上吵AI编程,主要分成三种人。第一种说“模型越强,程序员越闲,行业要消失了”;第二种说“模型一堆错,写出来的代码全是垃圾,根本不能用”;第三种比较高级一点,说“我们要负责任地使用AI”——但你接着追问什么叫“负责任”,对方就开始给你讲愿景。
Simon属于第四种——而且这一种人极少。他既不站“AI万能”也不站“AI无能”,他做的事情非常具体:把“如何负责任地使用AI写代码”这件事,拆成一套可以马上写进prompt、马上塞进CI、马上让团队照着做的工程动作。
你打开他的agentic engineering patterns系列文章,会发现他不在讨论“AI到底懂不懂软件工程”这种形而上的问题。他在讨论“在新session第一句话该让agent干什么”、“什么叫好的失败测试”、“为什么manual testing不能省”、“PR里要不要附截图和命令输出”。这些都是脏活,没有金句,发不了朋友圈,但全是直接能上工的东西。
我本人写过几年专栏,也见过太多在AI这个题目上“飘起来”的文章。Simon的价值,恰恰就在于他不飘。他用一个工程老手的姿态,把一个本来会被吹成神话的话题,砸回到工程层面。这种人不多,而且越往后越稀缺。
下面我按自己的理解,把Simon这一套讲清楚。讲完之后,我会再加一些他自己没明说、但在国内团队里同样要面对的现实问题。
一、Simon是谁,他凭什么这么讲
要看清Simon的判断分量,得先看清他的工程履历。这一点很多AI“专家”是经不起查的。
Simon是Django的共同创造者之一。Django这个东西用过的人都知道,它不是一个玩具框架,是过去十几年承载了无数生产系统的Web框架。能参与设计这种东西的人,一定见过大量真实世界里的项目腐烂、协作崩盘、维护噩梦。
Simon还是Datasette的作者。Datasette是围绕SQLite和数据新闻做的一整套开源工具链——你可以理解为,他不是只会写一两个工具,他维护着一个工具生态。一个长期维护开源工具的人,对“代码不可维护意味着什么”是有切身之痛的。
再加上他在被Eventbrite收购之前,是Lanyrd的工程合伙人;被收购后,他在Eventbrite做到engineering director;2002年开始他就一直在博客上写技术文章,到现在二十多年没断过。
这一切堆起来的画像非常清楚:他不是一个“AI产品体验官”,他是一个长期把软件真正交付到用户手里的人。
我为什么要先讲这个?因为今天讨论AI编程的人,太多没有真正交付过一个长期被用户使用、被团队维护、被替换升级、被回滚降级、被审计排查的项目。没有这种经验的人,看AI编程,看到的是demo;有这种经验的人,看AI编程,看到的是责任。
Simon看到的是后者。他几乎每一篇关于AI编程的文章,关键词都不是“模型”,而是“责任”、“交付”、“证据”、“审查”、“回滚”。这是一个工程老手的本能。
二、Simon的核心判断:写代码变便宜了,交付好代码并没有变便宜
Simon过去这一年最重要、也最被反复引用的一句话,浓缩成一句就是:写代码变便宜了,但交付好代码并没有变免费。
这一句话有两层意思。
第一层是事实陈述:coding agent确实把“敲代码”这件事的成本压到接近零。原本需要你花两小时写的样板,agent十秒钟生成。原本需要你查一下午文档才能拼出来的胶水,agent几句话搞定。这是事实,不需要争论。
第二层是判断:但所谓“好代码”的标准,并没有因此变松。Simon专门列过一份“好代码”的清单——能工作、可被证明能工作、解决正确问题、覆盖错误路径、足够简单、有测试保护、文档恰当、可维护。这份清单里的每一条,agent都可以帮你做一部分;但清单上的最终责任,没有任何一条可以从工程师身上挪走。
我想强调一下“没有任何一条可以挪走”这句话的分量。
我见过太多团队、太多个人,习惯性把责任甩给agent。代码出bug了,就甩“这是Cursor自动改的”;权限校验漏了,就甩“模型默认这么写的”;接口签错了,就甩“agent建议用这个名字的”。说实话,这种讲法只有一个意义——它告诉团队,你这个人是不可信的。
因为你是工程师。工程师的工作不是产出代码,是产出经过你证明的代码。你产出的每一行,无论是亲手敲的还是agent生成的,无论是开会决定的还是周末加班赶出来的,只要署你的名字提交了,那它就是你的责任。这一点和工具无关。
Simon有一篇文章干脆叫《Your job is to deliver code you have proven to work》——你的工作是交付经过你证明能工作的代码。这话听着像是常识,但你在国内任何一个互联网团队里坐两个礼拜,看十次代码评审,你会发现这“常识”根本就没被普遍接受。很多人之所以喜欢用AI,恰恰是因为它给了他们一个不再为代码负责的借口。 Simon要打破的,就是这个借口。
三、vibe coding不丢人,但它不能假装是软件工程
Simon对vibe coding的态度很有意思。他没有像很多老派工程师那样,一谈到vibe coding就咬牙切齿。他承认vibe coding在三种场景下是有价值的:低风险的一次性原型、新手入门、个人小工具。
这一点我同意。我自己写一些只在我电脑里跑的脚本,比如读取我自己的银行流水做汇总、给我自己的TODO做提醒、把昨天的会议纪要摘要一下,我也不写测试,也不重构,也不审。能跑就行。
但是问题不在vibe coding本身,问题在很多人把vibe coding当成所有AI辅助编程的代名词。
这就麻烦了。一个人在自己电脑上vibe一下没事,但他把vibe出来的代码丢到生产仓库里、丢到团队代码库里、丢到给客户的项目里——这就不是vibe coding了,这是用vibe coding的态度,干生产软件的活。
Simon反复强调一件事:vibe coding不是所有AI辅助编程的代名词。 真正负责任的AI辅助编程,开发者必须审查、测试、理解,并能向别人解释代码的行为。这是软件工程从来就有的标准,不是AI时代才出现的新要求。
他后来用agentic engineering来描述与vibe coding相反的那一端:有经验的工程师借助LLM加速工作,但仍然对交付的软件保持责任、理解和信心。这是一个对工程师友好的定义——它不否认你用AI,但它要求你保留工程师的姿态。
我特别欣赏Simon在这里画出来的那条线。那条线不是“用不用AI”的线,是“承不承担责任”的线。
承担责任的人,可以放心用AI。不承担责任的人,不用AI同样会出事。
很多团队的leader一上来就问“我们要不要禁用AI编程”——这个问题问错了。你应该问的是“我们的人,承担不承担自己署名提交代码的责任”。如果承担,那AI是放大器;如果不承担,那AI只是放大他们原来就有的不负责任。
四、Simon的工程哲学:context is king
Simon有一句被他反复说的话:“context is king”。
我先翻译一下:上下文是国王。
这话听着像废话,其实是一条非常硬的工程判断。它在说什么?它在说:在用LLM写代码这件事上,最大的杠杆不是你prompt写得多骚,而是你给模型喂的上下文准不准、全不全、对不对。
我们这个行业过去这一两年最大的认知偏差,就是把AI编程的核心能力误解成“prompt工程”。各种“骚prompt”、“魔法咒语”、“必杀提示词”在朋友圈刷屏。Simon对这些东西基本上是嗤之以鼻的——他几乎从不写“如何写出最好的prompt”,他写的是“如何把项目准备成一个适合agent工作的项目”。
这两个方向看起来都关心AI,但区别非常大。
第一种把杠杆放在那一句话上,希望靠一句神奇咒语让模型变聪明。第二种把杠杆放在整个工程环境上:测试、Git历史、文档、错误信息、CI、lint、preview环境、命名风格——这些早就存在的东西,决定了模型在你项目里能做到什么水平。
Simon说,agent会在你已有的代码风格里继续延展。你的测试写得乱,agent就会跟着写乱测试;你的命名风格统一,agent就会跟着统一命名;你的错误信息详细,agent修bug就修得快。你过去为人类同事建立的那一整套基础设施,在agent时代变成了agent的工作环境。
这件事的含义非常重磅。它意味着两件事:
第一,AI编程不会让“工程纪律”贬值,反而会让它显著升值。一个有良好测试、良好文档、良好CI的项目,agent能在里面快速、稳定、可验证地工作;一个测试残缺、文档过时、CI形同虚设的项目,agent只能在里面快速、不稳定、不可验证地搞破坏。
第二,“代码库即prompt”。你的代码库本身,就是给agent的最大的一段prompt。 你不用写在AGENTS.md里,agent扫一眼代码就明白了。所以,如果你想让agent帮你写出好代码,第一步永远是先把你的代码库变成一个能让agent学到好风格的地方。
这条原则Simon没明说出来,但他每篇文章其实都在围绕它打转。
五、Pattern 1:First run the tests——一句话把agent拉进项目状态
Simon最有代表性的几个pattern之一,是“First run the tests”。
四个词。中文翻译过来五个字:“先把测试跑了”。
就这五个字,干了五件事。
第一,它强迫agent发现项目的测试套件。 它得自己去找怎么跑测试,可能是pytest、可能是npm test、可能是go test ./…, 找的过程本身就是熟悉项目的过程。
第二,它让agent判断项目复杂度。 一个项目有30个测试还是3000个测试,agent跑一下就知道;它还能从测试组织方式里看出项目的边界、模块划分、对外接口。
第三,它给后续的所有改动建立了一个反馈机制。 一旦你让agent知道“这个项目有测试,而且我们重视它”,它后面每改一处,就会自动倾向于跑一下测试。这不是因为模型多聪明,是因为你已经给它建立了一个工作循环。
第四,它把agent拉进了“以测试为入口”的协作姿态。 这就像一个新人入职,你递给他的第一份材料是项目的README + 跑一遍CI——他还没开始干活,已经知道这个团队是怎么干活的。
第五,它让你提前发现问题。 如果agent发现测试本来就在挂,它会先报告给你,而不是在你“修一个不相关的bug”之后让CI翻车。
我特别喜欢Simon在这里展现出来的一个能力:他能把一个相当复杂的工程意图,压缩成一句agent就能听懂的短话。
这背后有个机制:前沿模型在大规模训练数据里早就见过“先跑测试再动手”这种工程习惯。你不需要解释完整流程,你只要用业内通行的术语。“First run the tests”这五个字之于agent,就像“先跑deploy”之于运维、“先复现bug”之于QA、“先看监控”之于SRE——它是一个工程暗号,触发的是模型已经理解的整套行为模式。
Simon有一个值得很多团队学的能力:把工程文化中的隐性规矩,变成可调用的几个词。
六、Pattern 2:Use red/green TDD——把“质量”压成一句prompt
Simon最经常被引用的另一个pattern是“Use red/green TDD”。
red/green TDD大家都知道:先写测试,看到红灯(失败),再写实现,看到绿灯(通过)。这是Kent Beck那一脉的test-driven development。
但是Simon这里有一个细节非常关键,我得专门讲一下:他本人原来不是test-first的拥护者。
他在介绍自己的Showboat和Rodney工具时坦白:自己整个职业生涯都对“测试优先、追求最高覆盖率”那一套有怀疑,他更喜欢“tests included”——也就是测试和实现一起交付,但不一定先写测试。
那他为什么还推荐agent用red/green TDD呢?因为agent的情境不一样。
人类做test-first,最大的成本是心流被打断。你脑子里好不容易有了一段实现思路,你硬要先去写测试,等于先把车熄火再启动,效率低,体验差。Simon就是这种工程师,他抗拒test-first是有他的道理的。
但是agent不一样。agent没有心流,agent不会觉得无聊,agent花两分钟写一个失败测试再写实现,对人类的体验来说几乎为零。Simon有一句话很扎心:他过去抗拒test-first是因为浪费的是自己的时间,但让agent做这件事就很好,因为浪费的是agent的时间。
更重要的是,TDD对agent还有一个独特的价值:它防止过度实现。
agent最大的毛病之一是太热情。你让它写一个简单功能,它会顺手给你加一个策略模式、一个工厂模式、再来一个观察者模式套着。这种“AI架构师综合症”在没有约束的场景下几乎是必然发生的。
但你一旦把任务变成“让这个失败测试通过”,整个agent的行为模式就会被收紧。它不再追求“漂亮的解决方案”,它追求“让红灯变绿”。这中间的差距是巨大的。
Simon的pattern化能力在这里又一次体现出来:他没有停留在“AI时代我们更需要测试”这种抽象判断,他把它压缩成一句短prompt。 这一句话能调用模型内部已经训练好的整套TDD知识,包括“先确认测试失败”、“实现只做最小改动”、“绿灯之后再重构”。
而且他特别提醒过一件细节:测试必须先失败。 如果你跳过红灯阶段,测试可能本来就过得了,那它就没证明任何东西,只是一个装饰品。这条提醒很多人不当回事,但实际上它是TDD和“凑测试覆盖率”之间唯一的分界线。
七、Pattern 3:Manual testing——自动测试不是“亲眼看见”
Simon最有辨识度的观点之一,也是我最想替他喊一遍的,是他对manual testing的坚持。
他在《Your job is to deliver code you have proven to work》里说,证明代码能工作有两个步骤,而且都不是可选项:第一是手动测试,第二是自动化测试。
我先把这一点拎清楚。Simon不是说“如果有时间再做manual testing”,他说的是“manual testing是必做的”。 这一点很多人会下意识跳过。
为什么必做?
因为自动测试通过,不等于软件能用。
我先举一个我自己见过的例子。某团队改了一个登录接口,单元测试全绿,集成测试全绿,CI亮着大绿灯。结果上线之后用户登不进去——因为测试用的是mock数据库,真实数据库的字段名跟测试fixture里的不一样。这种事在AI编程时代会变多,因为agent特别擅长“在它已经搭好的测试路径上把测试搞绿”,但它不一定知道真实环境里那些字段是怎么命名的。
再举一个更隐蔽的例子。一个UI组件改了样式,snapshot测试通过,因为它只验证HTML结构没变。但实际打开页面,因为CSS层级冲突,关键按钮被遮住了——agent不会“打开页面看一眼”,它只会“跑测试”。
自动测试和manual testing覆盖的是不同的风险。
自动测试覆盖的是“我已经知道要验证什么”的风险——你写过测试,所以你已经把行为预期固化了。
manual testing覆盖的是“我还不知道有什么问题”的风险——你打开真实系统,看到没预期到的状态、没预期到的报错、没预期到的UI。
这两类风险的存在性都不会因为AI到来就消失。事实上,AI到来之后,第二类风险还变多了——因为agent修代码非常快,一天能改几十个地方,每个地方都可能引出意料之外的连锁反应。
Simon的解法是agentic manual testing:让agent像人类QA一样实际操作软件。
具体怎么做?
- 对Python库,让agent用
python -c直接调用新函数,试边界情况; - 对JSON API,让agent启动开发服务器,用
curl探索; - 对Web UI,让agent用Playwright或自己的Rodney工具打开真实浏览器,点击按钮、读取accessibility tree、截图;
- 一旦在manual testing里发现问题,立刻让agent用red/green TDD把这个问题固化成永久回归测试。
这就形成了一个非常漂亮的闭环:
manual testing发现问题 → 写失败测试 → 修实现 → 测试通过 → 问题进入回归测试。
我希望你认真品一下这个闭环。它把manual testing和automated testing的对立给消解掉了——它让manual testing成为automated testing的“原料厂”。每一次manual testing发现的问题,都会被沉淀成长期的自动化资产。
这就是Simon的pattern思维:他从不停留在抽象判断,他总是把抽象判断转成可循环的工作流。
八、Pattern 4:Show your work——让agent留下证据
很多人对agent的“幻觉”问题有恐惧。其实在AI编程里,最危险的幻觉不是“代码写错了”——代码写错了你跑测试就会发现。最危险的幻觉是agent告诉你“我测试过了,没问题”,但它其实没真的测,它是根据预期编出来的结果。
Simon给这个问题的解法叫Show your work——让agent把它干的事情亮出来。
他做了一个工具叫Showboat。Showboat的核心机制非常简单:让agent在测试过程中构建一个Markdown文档,记录它执行了什么命令、得到了什么输出、看到了什么截图、验证了什么行为。每一项都是真实命令真实输出,不是agent“自我陈述”。
这工具的关键不是它的功能多复杂,而是它的设计原则。Simon自己提到,他见过agent在Markdown demo文件里直接编辑结果,而不是真去跑命令。所以工具本身就要防止agent作弊——exec 命令必须真的去跑命令、真的把stdout/stderr记进文档;agent不能“想象”一段输出然后写下来。
这背后是一个非常深刻的工程判断:在AI时代,code review不再只审代码,还要审证据。
我想把这一点讲透一点。在传统code review里,reviewer看的是代码本身——这一行写得对不对、命名规不规范、有没有边界bug、性能行不行。但在AI时代,这一套方法已经压不过来了。原因很简单:
- AI可以在十分钟里改五十处代码——你来不及一行行看;
- AI写的代码通常表面上很合规——它读过很多优秀代码,它知道“看起来怎样像是好代码”;
- 真正的问题往往不在代码本身,而在“这个代码到底有没有真的被执行过、真的覆盖了用户路径”。
这三条加在一起,意味着你必须把审查重心,从“代码本身”挪一部分到“行为证据”。
什么是行为证据?
- 一段真实的命令 + 真实的输出;
- 一张真实的截图 + 真实的页面状态;
- 一段真实的录屏 + 真实的交互流程;
- 一份真实的API请求 + 真实的响应;
- 一组真实的测试运行日志 + 真实的耗时和结果。
这些东西都是agent可以生成的,也是Showboat、Rodney这类工具被设计出来的目的。Simon在这里做的事,是把“我亲眼看过它运行”这件事,从主观声明变成了可复核的工件。
我特别想替Simon强调一下:这是code review在AI时代必须发生的最重要变化之一。 哪个团队最先把code review的SOP升级到“既审代码也审证据”,哪个团队就能在AI编程的浪潮里建立起真正的质量护城河。
九、Pattern 5:让agent模仿好习惯——把“代码库风格”当成隐性prompt
Simon有一条很现实的观察:LLM会奖励优秀的工程实践。
他举过一个特别接地气的例子:哪怕你的代码库里只有一两个你自己喜欢的测试样式,agent也会照着写。如果代码库整体高质量,agent通常也会按高质量的方式增量;如果代码库到处是脏活和反模式,agent就会继续复制脏活和反模式。
他甚至说过,他不太喜欢“写AGENTS.md逐条告诉agent怎么写代码”这种思路——更高杠杆的做法,是把整个项目本身做成一个agent能学到好风格的地方。
我个人非常认同这一条。理由很简单:
显式规则的容量是有限的,但隐性风格可以无限扩展。
你写一份AGENTS.md,再勤奋也就几页纸,再细致也覆盖不全所有场景。但你的代码库本身可能有几十万行——里面有几千个测试、几百个模块、上百份文档、几年的Git历史。这些东西agent全都能读、全都会模仿、全都会沉淀进它当前的工作策略。
所以Simon对“agent-ready项目”有非常具体的建议,我把它翻译成中文版的checklist:
- 能跑的自动化测试。 这是底线。一个项目如果没有agent能跑的测试,它本质上不能被agent可靠地协作。
- agent能调用的开发服务器/调试入口。 让agent能用
curl打你的API、能用Playwright访问你的页面、能用python -c调你的函数。可调用,agent才能闭环验证。 - lint / type check / formatter全套。 这些是agent生成代码后的“边界裁判”,它们的存在让agent能从外部反馈里自己纠偏,而不需要每次都靠人提醒。
- assertion失败信息要详细。 测试失败时,错误信息越具体,模型越容易修。这是一个被严重低估的工程细节——你那种
assert result == expected抛出来一行AssertionError、什么上下文都没有的测试,让人改都难,让agent改更难。 - 干净的测试样式 + 清晰的fixture。 agent会照着你已有的测试模仿。如果你已有的测试到处是重复setup、命名混乱、断言模糊,agent会原封不动地继承这种混乱。
- Git历史可读。 让agent能看到最近的commit message、看到改动的演进,理解“这个项目最近在做什么”。
说白了一句话:你想让agent写出好代码,先把你的项目变成一个让agent羞于写脏代码的地方。
这个原则的意义是反向的——它要求你和你的团队在AI到来之前,先把过去欠的工程债还掉。如果你过去的项目没有测试、没有文档、没有规范、没有CI,那么AI时代你不仅不会受益,反而会受害。因为agent会以更快的速度,把混乱再扩张一遍。
我多次说过:AI编程时代,过去的工程债会以更高的利息被结算。 Simon给这条判断提供了一个非常具体的实操路径。
十、Pattern 6:用Git管理agent的速度与风险
Simon对Git的强调,几乎到了“癖好”的程度。我特别想为这一点鼓掌。
agent的核心特征是快——它能在十几分钟内改几十个文件、动十几个模块。这件事的另一面是:错误也以同样的速度扩散。
人类工程师手抖一下,最多影响一个文件;agent手抖一下,可能跨越大半个仓库。你不能靠“小心一点”来抵御这种规模化的风险,你必须靠工具——而Git正是这个时代最被低估、最强大的工具之一。
Simon反复推荐的几个做法:
第一,新session用 “Review changes made today” 把agent拉进上下文。
这一句很短,但效果惊人。让agent先扫今天的commit log,它就会把“最近改了什么”作为后续动作的基础。这就像新人接手任务前,先看Git log + PR描述。Simon说的没错——agent通常非常懂Git,可以使用log、branch、reflog、bisect。 你不用解释,它就能用得很熟。
第二,每一个agent task都从一个干净分支开始。
这条不是Simon专利,是工程常识,但在AI编程时代它变得更重要。因为agent改动量大、不可预测,你不能让它直接动主分支。每个task一个分支,相当于每个task有一个隔离器——出了事,你可以毫不犹豫地丢弃。
第三,把高级Git工具下放到日常。
git bisect 是一个非常强大但学习曲线陡的工具——你需要写一个判定脚本、配合二分查找定位引入bug的commit。过去这种东西很多人一辈子用不上几次。但agent可以帮你把判定条件写出来、可以替你执行二分、可以总结结果。结果是:bisect从一个高门槛工具变成了一个日常工具。
这件事的更大意义是:AI不只是能写新代码,它还能把过去那些已经存在但学习成本高的工具,变得平民化。
Git、pytest、curl、Playwright、linter、CI、docker、bash——这些东西早就存在,门槛也早就在那里。agent没有发明新工具,但它降低了使用这些工具的门槛。一个普通工程师如今能调用的工具广度,是过去十年的好几倍。
我认识一些工程师在抱怨“AI让我的工作没价值了”。我对这种说法完全不认同。AI时代真正的杠杆,不是你有什么专属技能,而是你能不能让agent把整套软件工程工具都开动起来。 谁能让agent最熟练地使用最多种工具,谁就在新时代里有最大的产出杠杆。Simon在Git这件事上做的,就是这种放大。
十一、Anti-pattern 1:把未审查代码丢给别人
讲完六条pattern,得讲反模式。我先讲Simon本人最痛恨的那一条。
Simon反复反对的一种做法是:把agent生成的大量代码未经自己审查就提交PR,让同事或开源维护者替你收拾。
他说这种行为“非常常见,也非常令人沮丧”。他甚至说,如果你提交几百甚至几千行agent生成的代码,却没有确认它真的能工作,你其实是在把真正的工作委派给别人。
这一刀切得非常狠,我替他再补一刀。
我先把这条反模式的本质讲清楚:它的问题根本不是“用了AI”,它的问题是逃避责任。
逻辑是这样的:
- 你的同事可以自己用agent。
- 既然如此,你的价值是什么?
- 你的价值在于:理解问题、设计方案、约束agent、验证结果、清理实现、补上测试、解释取舍、给reviewer足够的上下文。
- 如果你只是把agent的输出转发给别人——你不是在用AI提高生产力,你是在用AI制造团队成本。
我把这话说得再直接一点:用agent写大量代码再不审就提PR的人,正在系统性地伤害团队。
为什么?因为他在转嫁责任。他自己不审,意味着reviewer要审;reviewer要审一段连作者本人都没确认过的代码,难度比审“作者已经手动测过”的代码高几倍——因为reviewer没有上下文,不知道哪里是改动核心,不知道哪里有过权衡,不知道哪里被验证过。
更糟的是,这种PR会让团队的review文化整体退化。当大家发现“PR里塞一堆未审的agent代码会浪费别人时间”,资深工程师会开始拒绝review新人的PR,新人会因此得不到反馈,新人就更不会成长。一个团队一旦把agent当甩锅工具,整个工程师培养机制就会崩盘。
Simon提出的“好的agentic engineering PR”标准非常清楚,我替他再总结一遍:
- 代码能工作,而且你有信心它能工作。 不是“测试好像过了”,是“我亲眼看过它跑过,我知道它的边界”。
- 改动足够小、可review。 一个PR一个意图。不要把agent三天的输出一次提交。
- 附带额外上下文。 上层目标、相关issue、设计取舍——告诉reviewer你为什么改、改到哪一步、哪些是被刻意保留的。
- agent写的PR描述也要审。 让别人读你自己都没读过的文字,是一种新的不礼貌。
这套标准非常适合制度化。我建议任何严肃团队都该把它刻进协作规范里:所有AI生成或AI辅助的PR,必须附带三类证据——自动化测试结果、手动测试说明、作者对关键实现的解释。
这样AI就不再是隐藏在背后的“神秘生产力”,它会进入可审查、可追责、可复盘的工程流程。一个团队真正成熟的标志,就是它能把AI从“黑箱里的加速器”变成“可被纪律约束的合作者”。
十二、Anti-pattern 2:不写测试,或者把测试当装饰
Simon对“不写测试”的态度,过去这一两年是越来越硬。
他原话之一是:现在还有人用coding agent写代码却完全不写测试,这是非常糟糕的想法。过去不写测试的理由是测试本身有维护成本,但在agent时代,测试几乎免费——agent能在几分钟里整理出一套像样的测试——因此再不写测试,纯粹就是工程偷懒。
但他同样警告“测试装饰化”。
什么是测试装饰化?就是测试存在的目的不是验证实现,而是让PR看起来专业。这种测试有几个识别特征:
- 测试用例多但覆盖路径浅;
- assert大量用
assert result is not None、assert len(x) > 0这种“反正不可能挂”的断言; - 用snapshot 替代行为断言——只验证结构形状,不验证业务规则;
- 一旦回滚实现,测试还能通过;
- 测试名都叫“test_should_work_correctly”——根本没说在测什么。
这种测试比没测试还危险。因为没测试至少诚实地告诉所有人“这个项目没保护”,而装饰性测试会给团队制造假的安全感。CI亮着绿灯,所有人觉得很安心,但其实任何回归都会顺利通过。
Simon提出的标准非常具体:自动化测试要和改动一起提交,而且如果回滚实现,测试应该失败。
这一句标准要狠狠地写进每个团队的review checklist。让reviewer养成习惯:拿到一个PR,先mental rollback一下实现,问一句“如果实现被还原,这些测试还能通过吗?” 如果还能通过,那这些测试就是装饰。退回去,重写。
在agent工作流里,TDD能进一步防止“测试装饰化”。因为TDD天生要求你先看到红灯——测试如果第一刻不能挂,那你这个测试就不成立。这个机制天生防御了“agent写一个永远不挂的测试糊弄人”这种行为。
Simon从一个原本不喜欢test-first的工程师,转向接受test-first,关键就在这一点:agent天然倾向于写过度的、装饰性的、不真正验证行为的代码,TDD是几乎唯一能从底层抑制这种倾向的工程纪律。
十三、Anti-pattern 3:把自动测试当作manual testing的替代品
第三个反模式我已经在前面铺垫过:自动测试不能替代manual testing。
Simon特别强调,他自己在发布前喜欢亲眼看到功能运行。这一点听起来很传统,但在agent时代更重要。原因很简单:
agent写测试的时候,很容易写出“覆盖自己实现路径”的测试,但漏掉真实用户路径。
我打个比方。假设你让agent改一个购物车的优惠券逻辑。agent写了一段实现,又顺手写了一些测试。这些测试通常会覆盖什么?覆盖agent自己想到的边界条件、覆盖agent自己理解的业务规则、覆盖agent自己写出来的代码分支。
但真实用户的路径是什么?是从首页点了“加入购物车”按钮、跳转到购物车、点击“使用优惠券”、选了一个特定的券、看到一个折扣金额。这一整套行为里,可能涉及前后端各五个组件、三个接口、两个数据库表。agent的测试只能覆盖其中一两块。
结果是:测试全绿 ≠ 用户能用。
Simon的推荐不是“更多单元测试”,而是“多层验证”。我把它整理成一个层次:
- 单元测试——证明局部逻辑;
- 集成测试——证明跨模块路径;
- manual testing——证明真实行为;
- 浏览器自动化(Playwright/Rodney)——证明UI;
- Showboat文档——证明过程;
- 截图/录屏——证明结果。
不同证据覆盖不同风险。一个PR里,至少要有一两层覆盖你不熟悉的真实行为。让agent用 curl 真打一次API、让agent用Playwright真点一次页面、让agent真截一张图——这些证据加在一起,能挡住绝大部分的“测试绿了但软件挂了”。
我最近在某个团队里就推这一条原则:任何涉及用户可见行为的PR,必须附带至少一个真实交互证据。 不是测试结果,是真实交互——一段curl输出、一张截图、一段playwright的trace文件。
这个规则上线之后,团队的线上事故下降得非常明显。原因不是工程师变聪明了,而是大家被迫把“真实运行一次”变成了PR的硬性步骤。
十四、Anti-pattern 4:YOLO mode缺少安全边界
Simon并不反对YOLO mode——也就是放手让agent去跑各种命令、不每一步都要批准。他承认,YOLO mode有非常大的生产力价值,因为不断请求人工批准会显著降低agent通过反复尝试解决问题的能力。
但他列了非常实在的风险:
- agent可能做出糟糕决策;
- agent可能受到prompt injection攻击;
- 最强大的工具往往是“在shell里执行命令”,所以一个失控的agent可以做很多人类用命令也能做的坏事;
- 错误的shell命令可以破坏文件系统;
- 攻击者可以通过prompt injection让agent泄露源码、环境变量、密钥;
- 你的机器甚至可能被当作攻击代理。
我看到很多团队在这一块毫无防备。他们让agent直接接触生产环境的credential、直接读取真实用户数据、直接连接生产数据库。这种做法在没出事之前看着没事,一旦出事,体量是灾难级的。
Simon的解法仍然是pattern化:
- 想放开agent,先放进sandbox。 别让它在你的本机直接乱跑,把它放到容器、虚拟机、Codespaces这种隔离计算环境里。
- 使用别人的隔离计算环境。 Codespaces、cloud coding agent这类服务专门为这种场景设计。
- credential最小权限。 给agent的是只读的数据库账号、只能访问测试桶的对象存储key、只能看分析数据的BI账号。
- 如果credential能花钱,就设预算上限。 Cloud key、API key、模型调用key——所有“花钱的”都设cap。这一条非常重要,YOLO mode + 没有预算上限 = 可能产生几千上万美元的事故。
- 尽量用test/staging数据,不用生产数据。 不只是为了安全,也是为了让manual testing真的能在受控环境里跑完。
Simon还反对一种更隐蔽的做法:拿敏感生产数据做测试。 他建议投资good mocking——一键创建随机用户、为特殊edge case创建模拟用户、为不同角色创建不同的fixture。
我特别同意这条建议。我们这个行业过去十几年,“用生产数据做测试”是被默许甚至鼓励的——理由是“只有真实数据才能测出真实问题”。但agent时代这条做法完全不能继续了。原因是:
- agent的访问粒度比人粗;
- agent受prompt injection影响;
- agent可以被“诱导”把数据外泄;
- agent的操作日志比人类难追溯。
四条加起来,意味着生产数据 + agent = 一个高风险组合。哪个团队还在这么干,就是在赌运气。
Simon在这里的pattern思维体现得也很清楚:他不是简单说“YOLO mode很危险,不要用”——他承认YOLO mode的生产力价值,然后给你列具体的隔离机制。 这是工程纪律的姿态:不是禁止能力,而是给能力套上边界。
十五、Pattern 7:Conformance-driven development——用多个实现反推出规范
Simon还有一个我觉得特别有启发性的实践:conformance-driven development。
他给Datasette加multipart file uploads的时候,干了这么一件事:让Claude构建一个“文件上传”的测试套件,要求这套测试在多个已有框架(Go、Node.js、Django、Starlette等)上都能跑过。然后再用这套测试去驱动Datasette的实现。
他自己原话是:“像是从六个已有实现反向工程出一个标准,再实现这个标准。”
这件事我觉得值得拿出来单讲。
过去写一个“conformance suite”是很费时的——你要研究多个实现、抽象共同约束、写大量测试用例。这种活通常是W3C、IETF这种标准组织在做,普通工程师没时间也没动力做。
但现在不一样。agent可以把这种活做得快得多。 它能把多个实现下载下来、跑一遍、抽出共同行为、写出测试套件。人类的价值则在于:选择参考实现、判断哪些行为属于规范、哪些只是偶然差异。
这是agent时代一个非常特别的工程能力——它能把“模糊需求”转成“可执行规格”。
我把这种能力拆成几种典型用法:
- TDD:把单个功能转成失败测试。 适合做新功能。
- Conformance-driven:把多个现实实现转成测试套件。 适合做替代实现、做兼容层、做协议适配。
- Manual-derived testing:把用户行为转成命令和截图。 适合做面向终端用户的产品。
- Showboat documentation:把测试过程转成证据文档。 适合做高合规要求的项目。
这四种方式都有一个共同点:它们都把“工程师脑子里那种模糊的‘我希望系统怎么工作’”,转成agent能执行、能验证、能复用的具体工件。
这就是Simon的真正贡献。他不是教你怎么用AI写代码,他是教你怎么把抽象工程经验沉淀成可调度的执行单元。
十六、Simon的组织启示:AI时代更需要senior engineering
讲到这里,得说一件特别违反直觉、但Simon非常坚持的判断:AI编程时代,对senior engineering的需求是上升的,不是下降的。
很多人担心AI会让初级工程师“被掏空”——既然agent能写代码,那初级工程师做什么?这种担忧不是没有道理。但Simon的视角不一样。他在Pragmatic Summit的炉边谈话里讲过一件很真实的事:同时驱动多个agent是非常耗脑的。
你需要不断切换项目、审查输出、给反馈、决定下一步、做权衡、设计验证、发现遗漏。这不是“靠AI偷懒”,这是要求你全力运转。
在《Vibe engineering》里,他把“会用AI的工程师”是怎么样的画得更清楚:
- 在研究方案;
- 在决定架构;
- 在写specification;
- 在定义成功标准;
- 在设计agentic loops;
- 在规划QA;
- 在管理一群“数字实习生”;
- 在做大量code review。
这些活,一条一条单独看,几乎都是senior engineer的特征。
所以在Simon的观察里,AI编程不是降低了工程标准,而是提高了工程师对“管理”和“验证”的要求。一个人可以同时启动几个agent,但瓶颈会从“你能不能写代码”转移到:
- 你能不能清楚定义任务?
- 你能不能提供足够上下文?
- 你能不能判断结果对错?
- 你能不能发现边界问题?
- 你能不能让agent证明它做对了?
- 你能不能把这一次的经验,沉淀成下一次可复用的prompt、测试、脚本或文档?
这套问题,全是senior工程师才有能力答的。AI让“敲键盘”贬值,但让“判断力”升值。 这件事我已经反复讲过多次,今天Simon用一个长期工程师的视角再确认一遍,我觉得分量很重。
Simon还提到一个我特别喜欢的概念:compound engineering loop。 它的意思是:每次agent session结束之后,把这次session里有效的经验沉淀下来,更新项目的README、AGENTS.md、测试模板、工具脚本、流程文档,让下一次agent运行得更好。
AI不会自己从过去的错误里学习,但是你的代码库、你的文档、你的测试、你的工具链,可以学习。 一个团队的agentic engineering成熟度,就反映在它的“compound engineering”做得有多好——这些可累积资产是不是越来越厚、越来越对、越来越能让新agent即用即上。
哪个团队最先建起这种compound engineering loop,哪个团队就在新时代里建立了真正的代差。这一条,是我想替Simon再用力喊一遍的。
十七、把Simon这套整理成一份可执行的工程清单
把Simon的相关言论压缩成可立刻上手的清单,大致是这八步。我用工程师本位的语气讲,希望你直接抄走用。
第一,开始之前先准备环境。
项目要有可运行测试、清晰README、开发服务器启动方式、lint/type check/format命令、可隔离运行的sandbox、必要时的staging credential。agent不是魔法,它需要工具和边界。 如果你跳过这一步,后面的所有努力都会被环境的脏乱抹平。
第二,新session先让agent进入上下文。
让它先跑测试,看Git最近变化,读相关测试,必要时用subagent探索代码库。不要一上来就让它写代码;先让它知道自己站在哪里。 一句“First run the tests”和一句“Review changes made today”加起来,能让你少踩很多坑。
第三,新功能用red/green TDD。
先写失败测试,再写实现,让测试变绿。这样既验证行为,又限制agent不要乱扩展。测试必须先失败,红灯阶段不能跳过。 这一点要写进团队规范,不写就会被略过。
第四,测试通过后做manual testing。
库函数用 python -c 或临时demo文件;API用 curl;Web UI用Playwright、Rodney或浏览器自动化;需要视觉判断时让agent截图自己检查。自动测试不是“亲眼看见”,亲眼看见才是亲眼看见。
第五,让agent留证据。
用Showboat或类似机制记录命令、输出、截图和说明。把“测试过”从主观声明变成可审查材料。reviewer审查的不只是代码,还有agent的行为证据。
第六,把发现的问题固化为测试。
manual testing发现bug,不仅让agent修,还要让它用red/green TDD写进回归测试。每一个被人类发现的问题,都应该变成一个永远不会再被同一个bug咬到的自动化资产。
第七,提交前自己review。
不要把agent输出原封不动丢给别人。PR要小、可解释、有上下文、有测试证据、有手动验证说明。agent写的PR描述也要审——让别人读你自己都没读过的文字,是新一代的不专业。
第八,复盘并沉淀。
把有效的prompt、测试模式、工具说明、失败经验、mock数据生成方法写进项目,让下一次agent更容易做对。AI不会从过去学习,但你的代码库可以——这就是compound engineering loop的全部秘诀。
这八步加起来,差不多就是一个团队从“用AI”升级到“用AI做工程”的最小路径。每一条都不复杂,每一条都很贵——贵的不是技术成本,是工程师改变习惯的成本。但谁先建立这套习惯,谁就在AI时代有真正的杠杆。
十八、我想替Simon再补一些他没说、但中文团队同样要面对的事
Simon写文章是面向英文世界的工程文化。他默认很多东西在那边是不需要解释的——比如code review的严肃性、比如PR的标准粒度、比如开源maintainer的责任感。在中文团队里,有几件事Simon没明说,但其实更需要被强调。我替他补几条:
第一,KPI和OKR体系不能只考核“产出代码量”。
很多公司今年已经开始用“agent生成代码量”作为效率指标。这是非常危险的。一旦“产出代码量”变成考核维度,工程师就会有动力把agent的输出原样丢出去——因为这能涨KPI。正确的考核维度应该是“被证明可工作并可维护的功能数量”,而不是“代码行数”。 这一点国内很多团队还没反应过来。
第二,code review文化要从“看代码”升级到“看证据”。
在一些组织里,code review本来就走形式,作者自己也不严格审查。AI时代如果还按这个走,就会出大事。要主动升级review的SOP:要求每个PR附带自动化测试结果、手动测试说明、关键实现解释。让Showboat-like工件成为PR的标准格式。
第三,“AI代码合规”是一个新岗位职责。
谁来确保团队提交的agent代码:
- 没有泄露敏感数据(agent可能把secrets打到日志里);
- 没有引入未授权依赖(agent可能装了一个有许可证问题的库);
- 没有违反公司架构规范(agent可能直接绕过中台调底层)。
这些都需要专门的人或者专门的CI规则盯着。很多团队会发现自己缺一个“AI编程治理岗”,这个岗位的雏形其实就是Simon说的agentic engineering pattern owner。
第四,老工程师的“经验沉淀”职责加重。
AI时代,老工程师最大的价值不是“自己写代码”,而是把自己的判断、经验、品味,沉淀成agent能用的资产——AGENTS.md、structural test、pre-commit hook、custom linter、onboarding doc。经验如果还停在老工程师脑子里,对组织来说就是负债;只有沉淀成系统资产,才是真资产。
这一点Simon用compound engineering loop表达过,但在中文团队里需要更明确:这是老工程师的新KPI。
第五,对实习生和初级工程师,要主动做“AI带教”。
不要让他们直接 vibe coding——他们会以为这就是工程师的全部工作。要让他们从一开始就接触到agentic engineering的纪律:先跑测试、TDD、manual testing、show your work、不丢未审PR。让他们的第一份工程肌肉记忆,就是“用AI还要负责任”。
这五条都是Simon没具体讲的,但对国内团队来说同样关键。它们的共同点是:把工程纪律从“个人习惯”上升到“组织能力”。 Simon提供的是个人级别的pattern,把它扩展成组织级别的制度,是中国团队下一步要做的功课。
结语:Simon真正的贡献,是把AI编程拉回了软件工程
讲到这里,我觉得可以收尾了。
Simon Willison的独特性不在于“他说AI很强”,也不在于“他说AI很危险”。这两种声音都很多。Simon真正有价值的地方,是他把AI编程从争论拉回了软件工程。
他不满足于“我们要负责任地使用AI”这种正确但空泛的话。他把它拆成了一组patterns:
- First run the tests.
- Use red/green TDD.
- Test with curl.
- Test with Playwright.
- Look at screenshots.
- Use Showboat to leave evidence.
- Don’t file unreviewed PRs.
- Keep tests clean.
- Let the agent imitate good patterns.
- Run in a sandbox.
- Use tight credentials.
每一条都能立刻执行。每一条都能写进团队规范。每一条都能放进CI、放进review checklist、放进入职培训。每一条都把抽象的“工程纪律”变成了可调用的、可被强制执行的工程动作。
如果说AI编程的早期阶段是“看,模型能写代码!”,那么Simon代表的是下一阶段——“现在我们该如何证明这些代码值得交付?”
这句话听上去保守,但其实非常深。它把焦点从“产能”挪回了“交付”——从“我们能写多少”挪回了“我们能稳定交付多少”。这是任何一个真正经历过软件工程长期周期的人,都会本能认同的视角。
AI让写代码的成本下降了,但软件工程从来不只是写代码。真正稀缺的,是知道该写什么、怎样证明它工作、如何让别人安全地接手、如何让系统在未来继续可维护。
这些事情,Simon在用一组小而具体的pattern一件件地教给我们。
他不教大道理,他教暗号。
下一次你打开Cursor、Codex、Claude Code,进入一个新session,记得先打这五个字:
First run the tests.
这就是Simon想要你养成的肌肉记忆。把这条记下,把这条做实,剩下的整套agentic engineering,都会自然长出来。
至于愿不愿意把它做实,那就是你的选择了。但如果你选择不做实——别说Simon,连我,都帮不了你。
工程纪律这件事,从来都不是别人能替你完成的。