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 的作者——围绕 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 就咬牙切齿,而是承认它在三种场景下有价值:低风险的一次性原型、新手入门、个人小工具。
这一点我同意。我自己写一些只在我电脑里跑的脚本,比如读取我自己的银行流水做汇总、给我自己的 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。 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 拉进了“以测试为入口”的协作姿态。 就像新人入职,你递给他的第一份材料是项目的 README 加跑一遍 CI——还没干活,就已经知道这个团队怎么干活的了。
它还让你提前发现问题。 如果测试本来就在挂,agent 会先报告,而不是等你“修一个不相关的 bug”之后才让 CI 翻车。
Simon 有一个值得很多团队学的能力:把一个相当复杂的工程意图,压缩成一句 agent 就能听懂的短话。
背后的机制是:前沿模型在训练数据里早就见过“先跑测试再动手”这种工程习惯。你不需要解释完整流程,只需要用业内通行的术语。“First run the tests”之于 agent,就像“先看监控”之于 SRE——它是一个工程暗号,触发的是模型已经理解的整套行为模式。
六、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,最大的代价是心流被打断——脑子里好不容易有了一段实现思路,硬要先写测试,等于把车熄火再启动。但 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 是必做的”,不是“如果有时间再做”。 很多人会下意识跳过这一步。
为什么必做?
因为自动测试通过,不等于软件能用。
举个我见过的例子。某团队改了一个登录接口,单元测试全绿,集成测试全绿,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,核心机制很简单:让 agent 在测试过程中构建 Markdown 文档,记录执行了什么命令、得到了什么输出、看到了什么截图、验证了什么行为。每一项都是真实命令真实输出,不是 agent“自我陈述”。
关键不是工具的功能多复杂,而是设计原则。Simon 提过,他见过 agent 在 Markdown demo 文件里直接编辑结果,而不是真去跑命令。所以工具本身就要防作弊——exec 命令必须真正执行、把 stdout/stderr 记进文档;agent 不能“想象”一段输出然后写下来。
这背后是一个非常深刻的工程判断:在 AI 时代,code review 不再只审代码,还要审证据。
把这一点展开说。在传统 code review 里,reviewer 看的是代码本身——这一行对不对、命名规不规范、有没有边界 bug、性能行不行。但在 AI 时代,这套方法已经压不过来了:
- AI 可以在十分钟里改五十处代码——你来不及一行行看;
- AI 写的代码通常表面上很合规——它读过很多优秀代码,它知道“看起来怎样像是好代码”;
- 真正的问题往往不在代码本身,而在“这个代码到底有没有真的被执行过、真的覆盖了用户路径”。
这三条加在一起,意味着你必须把审查重心,从“代码本身”挪一部分到“行为证据”。
什么是行为证据?
- 一段真实的命令 + 真实的输出;
- 一张真实的截图 + 真实的页面状态;
- 一段真实的录屏 + 真实的交互流程;
- 一份真实的 API 请求 + 真实的响应;
- 一组真实的测试运行日志 + 真实的耗时和结果。
这些东西都是 agent 可以生成的,也是 Showboat、Rodney 这类工具的设计目的——把“我亲眼看过它运行”从主观声明变成可复核的工件。
这是 code review 在 AI 时代必须发生的最重要变化之一。 哪个团队最先把 review 的 SOP 升级到“既审代码也审证据”,哪个团队就能建起真正的质量护城河。
九、Pattern 5:让 agent 模仿好习惯——把“代码库风格”当成隐性 prompt
前面讲过“代码库即 prompt”,Simon 在实操层面把这件事落得更细。他有一条很现实的观察:LLM 会奖励优秀的工程实践。
他举过一个接地气的例子:哪怕代码库里只有一两个你喜欢的测试样式,agent 也会照着写。代码库整体高质量,agent 就按高质量方式增量;到处是脏活和反模式,agent 就继续复制脏活和反模式。
他甚至说过,不太喜欢“写 AGENTS.md 逐条告诉 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 最熟练地使用最多种工具,谁就有最大的产出杠杆。
十一、Anti-pattern 1:把未审查代码丢给别人
讲完 pattern,得讲反模式。先说 Simon 最痛恨的那一条。
Simon 反复反对的一种做法是:把 agent 生成的大量代码未经自己审查就提交 PR,让同事或开源维护者替你收拾。
他说这种行为“非常常见,也非常令人沮丧”。他甚至说,如果你提交几百甚至几千行 agent 生成的代码,却没有确认它真的能工作,你其实是在把真正的工作委派给别人。
这一刀切得很狠,我再补一刀。
这条反模式的本质不是“用了 AI”,而是逃避责任。逻辑很简单:你的同事自己也能用 agent,那你的价值在哪?在于理解问题、设计方案、约束 agent、验证结果、清理实现、补上测试、解释取舍、给 reviewer 足够上下文。如果你只是转发 agent 的输出——你不是在提高生产力,你是在制造团队成本。
我把这话说得再直接一点:用 agent 写大量代码再不审就提 PR 的人,正在系统性地伤害团队。
为什么?因为他在转嫁责任。自己不审,reviewer 就得审——而 reviewer 面对的是一段连作者都没确认过的代码,难度是正常 review 的好几倍,因为缺少上下文、不知道哪里是改动核心、不知道哪里被验证过。
更糟的是,这种 PR 会让团队 review 文化整体退化。资深工程师开始拒绝 review 这种 PR,新人因此得不到反馈,更不会成长。团队一旦把 agent 当甩锅工具,整个工程师培养机制就会崩盘。
Simon 提出的“好的 agentic engineering PR”标准很清楚:
- 代码能工作,而且你有信心。 不是“测试好像过了”,是“我亲眼看过它跑,我知道它的边界”。
- 改动足够小、可 review。 一个 PR 一个意图,不要把 agent 三天的输出一次提交。
- 附带额外上下文。 上层目标、相关 issue、设计取舍——让 reviewer 知道你为什么改、改到哪步、哪些是刻意保留的。
- agent 写的 PR 描述也要审。 让别人读你自己没读过的文字,是一种新的不礼貌。
这套标准非常适合制度化。我建议严肃团队把它刻进协作规范:AI 生成或 AI 辅助的 PR,必须附带三类证据——自动化测试结果、手动测试说明、作者对关键实现的解释。
这样 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 为什么不可替代,这里从反面再补一刀:agent 写测试的时候,很容易写出“覆盖自己实现路径”的测试,但漏掉真实用户路径。
打个比方。你让 agent 改购物车的优惠券逻辑,它写了实现又顺手写了测试。这些测试覆盖什么?覆盖 agent 自己想到的边界条件、自己理解的业务规则、自己写出来的代码分支。但真实用户路径是:从首页加购物车→跳转→点“使用优惠券”→选一个特定券→看到折扣金额。这条路径可能涉及前后端各五个组件、三个接口、两个数据库表。agent 的测试只能覆盖其中一两块。
测试全绿 ≠ 用户能用。
Simon 推荐的不是“更多单元测试”,而是多层验证:单元测试证明局部逻辑,集成测试证明跨模块路径,manual testing 证明真实行为,浏览器自动化证明 UI,Showboat 文档证明过程,截图录屏证明结果。不同证据覆盖不同风险,一个 PR 至少要有一两层覆盖你不熟悉的真实行为。
我最近在一个团队里推了一条规则:涉及用户可见行为的 PR,必须附带至少一个真实交互证据——一段 curl 输出、一张截图、一段 Playwright trace。不是测试结果,是真实交互。规则上线后线上事故降得很明显,原因不是工程师变聪明了,而是大家被迫把“真实运行一次”变成了 PR 的硬性步骤。
十四、Anti-pattern 4:YOLO mode 缺少安全边界
Simon 并不反对 YOLO mode——放手让 agent 跑命令、不每步都审批。他承认 YOLO mode 有很大的生产力价值,因为频繁请求人工批准会显著降低 agent 通过反复试错解决问题的能力。
但他列了很实在的风险:agent 可能做出糟糕决策、受 prompt injection 攻击;最强大的工具往往是 shell 执行,失控的 agent 什么都干得出来;错误命令可以破坏文件系统;攻击者可以通过 prompt injection 让 agent 泄露源码、环境变量、密钥;你的机器甚至可能被当作攻击代理。
我看到很多团队在这一块毫无防备——agent 直接接触生产 credential、读取真实用户数据、连接生产数据库。没出事之前看着没事,一旦出事就是灾难级的。
Simon 的解法仍然是 pattern 化:
- 想放开 agent,先放进 sandbox。 容器、虚拟机、Codespaces——别让它在你的本机直接乱跑。
- credential 最小权限。 只读数据库账号、只能访问测试桶的存储 key、只能看分析数据的 BI 账号。
- 花钱的 credential 设预算上限。 Cloud key、API key、模型调用 key——YOLO mode 加没有预算上限,等于开着一台烧钱机器。
- 尽量用 test/staging 数据。 不只为了安全,也为了让 manual testing 在受控环境里跑完。
Simon 还反对一种更隐蔽的做法:拿敏感生产数据做测试。 他建议投资 good mocking——一键创建随机用户、模拟 edge case 用户、为不同角色创建 fixture。
我们这个行业过去十几年,“用生产数据做测试”是被默许甚至鼓励的——理由是“只有真实数据才能测出真实问题”。但 agent 时代这条做法必须收紧。agent 访问粒度比人粗、受 prompt injection 影响、可以被诱导外泄数据、操作日志比人类难追溯。四条加起来,生产数据加 agent 就是高风险组合。还在这么干的团队,是在赌运气。
Simon 的姿态始终一致:不是禁止能力,是给能力套上边界。
十五、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 结束后,把有效经验沉淀下来——更新 README、AGENTS.md、测试模板、工具脚本、流程文档——让下一次 agent 运行得更好。
AI 不会自己从过去的错误里学习,但你的代码库、文档、测试、工具链可以。一个团队的 agentic engineering 成熟度,就反映在这些可累积资产是不是越来越厚、越来越对、越来越能让新 agent 即用即上。
最先建起 compound engineering loop 的团队,会在新时代里拥有真正的代差。
十七、把 Simon 这套整理成一份可执行的工程清单
把 Simon 的要点压缩成可立刻上手的清单,大致八步。直接抄走用。
第一,准备环境。 项目要有可运行测试、清晰 README、开发服务器启动方式、lint/type check/format 命令、可隔离运行的 sandbox。agent 不是魔法,它需要工具和边界。
第二,让 agent 进入上下文。 先跑测试、看 Git 最近变化、读相关代码。“First run the tests”加“Review changes made today”,两句话能省很多坑。
第三,新功能用 red/green TDD。 先写失败测试,再写实现。测试必须先失败,红灯阶段不能跳过。
第四,测试通过后做 manual testing。 库函数用 python -c,API 用 curl,Web UI 用 Playwright 或浏览器自动化。自动测试不是“亲眼看见”。
第五,让 agent 留证据。 用 Showboat 或类似机制记录命令、输出、截图。把“测试过”从主观声明变成可审查材料。
第六,把发现的问题固化为测试。 manual testing 发现 bug,用 red/green TDD 写进回归测试。每一个被人类发现的问题都应该变成自动化资产。
第七,提交前自己 review。 PR 要小、可解释、有上下文、有证据。agent 写的 PR 描述也要审。
第八,复盘并沉淀。 有效的 prompt、测试模式、工具说明、失败经验写进项目,让下一次 agent 更容易做对。这就是 compound engineering loop。
这八步加起来,就是一个团队从“用 AI”升级到“用 AI 做工程”的最小路径。每条都不复杂,每条都很贵——贵在工程师改变习惯的成本。但谁先建立这套习惯,谁就有真正的杠杆。
十八、Simon 没说、但中文团队同样要面对的事
Simon 写文章面向英文工程文化,他默认 code review 的严肃性、PR 的标准粒度、开源 maintainer 的责任感这些东西不需要解释。在中文团队里,有几件事需要额外强调。
第一,KPI 和 OKR 体系不能只考核“产出代码量”。
很多公司今年已经开始用“agent 生成代码量”作为效率指标。这是危险的。一旦代码量变成考核维度,工程师就有动力把 agent 输出原样丢出去。正确的考核应该是“被证明可工作且可维护的功能数量”,不是代码行数。
第二,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 提供的是个人级别的 pattern,扩展成组织级别的制度,是下一步要做的功课。
结语:把 AI 编程拉回软件工程
收尾了。
Simon Willison 的独特性不在于“他说 AI 很强”或“他说 AI 很危险”——这两种声音都不缺。他真正有价值的地方是把 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 代表的是下一阶段——“这些代码怎么证明值得交付?”
这话听上去保守,其实很深——焦点从“产能”挪回了“交付”,从“我们能写多少”挪回了“我们能稳定交付多少”。经历过软件工程长期周期的人,都会本能认同这个视角。
写代码的成本下降了,但软件工程从来不只是写代码。真正稀缺的,是知道该写什么、怎样证明它工作、如何让别人安全接手、如何让系统在未来可维护。 Simon 在用一组小而具体的 pattern 一件件地教这些事。
他不教大道理,他教暗号。
下一次你打开 Cursor、Codex、Claude Code,进入新 session,记得先打这五个字:
First run the tests.
这就是 Simon 想让你养成的肌肉记忆。把这条做实,剩下的整套 agentic engineering 都会自然长出来。
至于愿不愿意做实——那是你的选择。但如果你选择不做,别说 Simon,连我都帮不了你。
工程纪律从来都不是别人能替你完成的。