Simon Willison 教我的事:你交付的不是代码,是被你证明过的代码

Simon Willison 教我的事:你交付的不是代码,是被你证明过的代码

一、那个让我从此对 AI 编程“乐观但警觉”的下午

我有个朋友,一年多前给我打过一通电话。

那是 2024 年底的事。他在一家中等规模的 SaaS 公司做后端,团队刚把 Cursor 全员铺开。他兴冲冲地跟我说:哥们,太爽了,我现在一天的产出顶过去三天,PR 也提得飞快。

我问他:“那你的 bug 率呢?”

他沉默了一会,像是从手机另一头笑了一声:“这个嘛,最近确实多了点……”

“具体多了多少?”

“翻倍。”

“那你的修 bug 时间呢?”

更长的沉默。然后是一句让我印象很深的话:“基本也翻倍了。”

我说:“那你净生产力大概是零?”

他在电话另一头开始大骂 Cursor、骂 Claude、骂“AI 根本就是鸡肋”。骂了五分钟。

骂完之后,他静下来问我:“那你说怎么办?”

那是 2024 年的最后一个礼拜。我没有特别好的答案。但我记得那天晚上,我打开了 Simon Willison 的博客,从最近的几篇翻起。Simon 那段时间正在密集写关于 coding agent 的实战经验——他不是写“AI 多牛”,也不是写“AI 多坑”,他写的是一个有二十多年 Web 工程经验的人,怎么在跟 agent 合作的过程中,把工程纪律一条一条恢复回来

那一夜我读了大概六七篇。读完有一个很清晰的感觉:Simon 写的就是答案,但答案是反潮流的。 大多数人 2024 年想要的答案是“哪个模型最强、哪个 prompt 最骚、哪个新工具最快”。Simon 给的答案是“先把测试跑了”、“先红再绿”、“先手动试一下”、“PR 里附上证据”——全是软件工程教科书里就有的东西,只不过换上了 AI 时代的新外套。

我把链接转给了那个朋友。他看完跟我说:“这……不就是我们以前都知道的工程实践吗?”

我说:“对。但你现在没在做。你的 Cursor 能干那么多活,你的工程纪律却退到了 2014 年。所以你才在跟 AI 对赌——而且赌输了。”

我们后来又讨论了很多次。他团队这一年慢慢把 Simon 的那一套 patterns 揉进自己的工作流。到了 2026 年初,他打来电话,第一句话是:“哥们,我们的修 bug 时间,回到正常了。”

这就是这篇文章想讲的东西。Simon Willison 不是教你怎么用 AI 写更多代码,他是教你怎么用 AI 写更少的、更值得交付的代码。

读懂这一点,比读懂任何一个模型 benchmark 都重要。


二、先回答一个问题:Simon 凭什么这么讲?

每次有人在网上发“AI 编程心得”,我习惯先看他写没写过被真实用户使用的软件。

很多“AI 意见领袖”经不起这一关。他们的工程经历可能是几个 toy project、几个 tutorial fork、再加几个开源贡献——没毛病,但跟“长期维护一个被真实用户使用的项目”是两件事。

Simon 过得了这一关。他的简历——

  • Django Web Framework 的共同创造者;
  • Datasette 的作者,长期围绕数据新闻、SQLite 和开源工具做开发;
  • 在被 Eventbrite 收购之前,是 Lanyrd 的工程合伙人,被收购后做到 Eventbrite 的 engineering director;
  • 2002 年开始坚持写技术博客,到现在二十几年没断。

这是一份非常硬的工程履历。最后一条我要特别强调——写技术博客二十几年没断。 你知道这有多难吗?我自己写了十多年的 Joel on Software,深知这个频率有多累。能坚持二十多年的人,不是“AI 风口”上随便冒出来的网红——他是一个把“思考公开化”当成习惯的人。

为什么要先确认这件事?因为这决定了我读他文章时给多少权重。一个长期承担工程责任的老手谈 AI,和一个把 AI 当 demo 拍视频的 KOL 谈 AI,完全是两码事。 前者会本能地把焦点放在“交付”和“维护”上;后者会本能地追求“看起来酷不酷”。

Simon 属于前者。所以他每写一篇关于 coding agent 的文章,关键词都不是“模型多神”,而是“责任”、“证据”、“审查”、“回滚”、“边界”。这是工程师的本能。


三、Simon 这一年的核心判断,一句话就能说完

我把 Simon 过去这一年那么多文章浓缩成一句话:写代码变便宜了,但交付好代码并没有变免费。

在《Writing code is cheap now》里,Simon 说,coding agent 大幅降低了“把代码打进编辑器”的成本。这件事会扰乱我们过去关于时间、设计、重构、测试和文档的所有直觉

你有没有用过老式打字机?没有也没关系。我用过。打字机有一个特点:它逼着你思考。 因为打错一个字要换纸或者用修正液,你下笔之前会先想一遍。后来有了 word processor,删除一个字只需要按一下键——人们以为这是巨大的解放,但它顺便也消解了“动笔之前先想清楚”的习惯。

打字机时代的写作和 word processor 时代的写作,是两种不同的写作。我们正在经历的,是一模一样的事。

过去“敲代码”的成本,在我们脑子里默默扮演了一个“思考之前请先思考”的角色。 我们之所以会先在脑子里设计、先画一画图、先想一想边界——很大一部分是因为“敲代码”这件事本身有摩擦。摩擦让我们慢下来,让我们考虑投资回报。

agent 把这个摩擦消除了。“敲代码”几乎免费了。

好处是巨大的——很多过去因为“懒得敲”而没做的小工具、小实验、小验证,现在都能跑起来了。但坏处也是巨大的——我们过去用来权衡“这件事值不值得做”的直觉,开始系统性失效。

Simon 在这里的判断我认为是这一年最有分量的工程判断之一:“敲代码便宜了”≠“交付好代码便宜了”。 因为“好代码”的标准没有因此变松。他甚至专门列了一份“好代码”清单:

  • 能工作;
  • 可被证明能工作;
  • 解决了正确的问题;
  • 异常和边界条件可预测;
  • 足够简单,最小化;
  • 有测试保护;
  • 文档恰当且与现状一致;
  • 为未来变化保留余地但不过度设计;
  • 项目所需的各种“-ility”——安全性、可靠性、可观测性、可维护性。

这份清单的精彩之处不在于它列了什么,而在于一个事实:清单上的每一条,agent 都可以帮你做一部分。但最终责任,没有任何一条可以从工程师身上挪走。

这两句话是后面所有 patterns 的精神基础。


四、Vibe coding 不丢人,但请你别把它叫“软件工程”

每次跟人聊 AI 编程,“vibe coding”这个词都会冒出来。

vibe coding 是 Karpathy 提出来的概念,简单讲就是:让 LLM 写代码,但你不审查它写了什么、不真正理解它写了什么、把“看起来能跑”当作终点。

先承认一件事:我自己也偶尔 vibe coding。 写一些只在我电脑上跑的小脚本——把昨天的银行流水做汇总、给 TODO 做提醒、把会议纪要摘要——我从来不审,从来不写测试,能跑就行。连 README 都懒得写。

Simon 也承认同样的事。他说 vibe coding 在三类场景下有价值:低风险的一次性原型、新手入门、个人小工具。

但是——这种态度只能在它的边界内被允许。

一旦你把 vibe 出来的代码丢到生产仓库、丢到团队代码库、丢给客户用,性质就完全变了。这不再是 vibe coding,这是用 vibe coding 的态度干生产软件的活。两者的差距,跟在自家厨房做饭和开餐厅是一回事——同样是炒一盘菜,但责任完全不同。

Simon 反复强调:vibe coding 不是所有 AI 辅助编程的代名词。 真正负责任的 AI 辅助编程要求开发者审查代码、理解代码、测试代码、能向别人解释代码的行为。

注意最后一条——“能向别人解释”。这是软件工程从来就有的标准。如果你写的代码自己都解释不了,它就不可维护。从 COBOL 时代到现在,这一条从来没变过。Simon 做的,是把这个老标准重新塞回 agent 协作的语境里。

他后来还提出过一个半开玩笑的词——“vibe engineering”——描述与 vibe coding 相反的那一端:有经验的工程师借助 LLM 加速工作,但仍然对交付的软件保持责任、理解和信心。到 2026 年,他更倾向于用“agentic engineering”这个词。

我个人很喜欢这条线。它把“用不用 AI”这个伪问题给消解了——真正的问题是“承不承担责任”。

承担责任的人,可以放心用 AI。不承担责任的人,不用 AI 同样会出事。

很多团队 leader 一上来就问“我们要不要禁 AI”。这个问题问错了。你应该问的是:“我们的人,是否对自己署名提交的代码负责?”如果负责,AI 是放大器;如果不负责,AI 只是放大他们本来就有的不负责。

这一点和工具无关,跟工程师的人格有关。


五、Context is king——别再追求骚 prompt 了

Simon 有一句被他反复说的话:“context is king”。

上下文是国王。听起来像废话。其实不是。

设想你刚加入一家新公司。第一天,HR 给你两份材料:

第一份:一份“如何成为我们公司的优秀员工”的二十页 PDF。
第二份:你部门过去半年的所有内部 Slack 对话、所有 PR、所有设计文档、所有 postmortem、所有 onboarding doc。

哪一份让你更快地像个老员工一样工作?

显然是第二份。第一份是“指南”,告诉你“应该怎么做”;第二份是“上下文”,让你“知道现在到底在做什么、为什么这么做”。这两者的差距,就是“prompt 工程”和“context 工程”的差距。

我们这个行业过去一年最大的认知偏差,是把 AI 编程的核心能力理解成“写出最骚的 prompt”。各种“必杀 prompt”、“魔法咒语”、“高级模板”在朋友圈刷屏。Simon 对这些东西基本嗤之以鼻——他几乎从不写“如何写出最好的 prompt”,他写的是“如何把项目准备成一个适合 agent 工作的项目”。

这两个方向看起来都关心 AI,但区别很大。前者把杠杆放在“那一句话”上,指望靠一句神奇咒语让模型变聪明;后者把杠杆放在整个工程环境上:测试、Git 历史、文档、错误信息、CI、lint、preview 环境、命名风格——这些早就存在的东西,决定了模型在你项目里能做到什么水平。

Simon 的观察是:agent 会在你已有的代码风格里继续延展。你的测试写得乱,agent 就跟着写乱测试;你的命名风格统一,agent 就跟着统一命名;你的错误信息详细,agent 修 bug 就修得快。

这意味着什么?意味着AI 编程不会让“工程纪律”贬值,反而会显著升值。 一个有良好测试、良好文档、良好 CI 的项目,agent 能在里面快速、稳定、可验证地工作;一个测试残缺、文档过时、CI 形同虚设的项目,agent 只能在里面快速、不稳定、不可验证地搞破坏。

我在 Fog Creek 的时候花了大量时间写“我们到底是怎么做事的”——FogBugz、Stack Overflow、Trello,每一个产品都有内部文档。但说实话,那些文档大部分时间是没人看的——新人上手最快的方式,永远是看代码本身、看历史 commit、看现有测试。

这件事到今天没有变。只不过“看代码学规矩”的主体,从人变成了 agent。我们过去为人类写的代码库纪律,现在自动变成了“AI 协作纪律”。这是个意外的红利——前提是你过去做了。


六、Pattern 一:First run the tests——四个英文单词的魔法

我把 Simon 的几个核心 pattern 逐个讲。

第一个叫“First run the tests”——翻成中文就是“先把测试跑了”。Simon 每次在已有项目里开新 agent session,常常第一句话就是这个。

别小看这四个词,它同时干了好几件事——

让 agent 发现项目的测试套件。agent 得自己去找怎么跑测试,可能是 pytest、可能是 npm test、可能是 go test ./…。找的过程本身就是在熟悉项目。它让 agent 一上来就判断项目的体量——30 个测试和 3000 个测试是两种生物,agent 跑一下就知道了。

更重要的是,它给后续所有改动建立了反馈机制。一旦 agent 知道“这个项目有测试,而且我们重视它”,后面每改一处,就会自动倾向跑一下测试。这不是模型多聪明,而是你已经把它带进了一个工作循环。就像新人入职第一天,你递给他的第一份材料是 README + 跑一遍 CI——他还没干活,已经知道这个团队是怎么干活的。

还有一个附带好处:提前发现既有问题。如果测试本来就在挂,agent 会先报告,而不是在你“修一个不相关的 bug”之后让 CI 翻车。

我特别欣赏 Simon 的一个能力:他能把一个相当复杂的工程意图,压缩成 agent 就能听懂的几个词。

为什么这种压缩能行?因为前沿模型在大规模训练数据里早就见过“先跑测试再动手”这种工程习惯。你不需要解释完整流程,只要用业内通行的术语。“First run the tests”之于 agent,就像“先跑 deploy”之于运维、“先复现 bug”之于 QA、“先看监控”之于 SRE——它是一个工程暗号,触发的是模型已经理解的整套行为模式。

很多团队 leader 能讲出 100 页的工程哲学,但讲不出能直接抄的“开局五个字”。Simon 反过来——他给你五个字,但每个字都重得像砖头。


七、Pattern 二:Use red/green TDD——把“质量”压成一句 prompt

Simon 另一个核心 pattern 叫“Use red/green TDD”——红绿测试驱动开发。

red/green 大家都懂:先写测试,看到红灯(失败),再写实现,看到绿灯(通过)。Kent Beck 那一脉的 test-driven development。

但这里有个关键细节:Simon 本人原来不是 test-first 的拥护者。

他坦白过:整个职业生涯都对“测试优先、追求最高覆盖率”那一套有怀疑,他更喜欢“tests included”——测试和实现一起交付,但不一定先写测试。

那他为什么还推荐 agent 用 red/green TDD?

这里有一个精彩的认知反转。

人类做 test-first,最大的成本是心流被打断。你脑子里好不容易有了一段实现思路,硬要先去写测试,等于先把车熄火再启动,效率低,体验差。Simon 自己也这么想,他有他的道理。

但 agent 不一样。agent 没有心流,agent 不会觉得无聊。 它花两分钟先写一个失败测试再写实现,对你来说几乎没有额外心理负担——浪费的不是你的时间,是 agent 的时间。Simon 说过一句话我每次想到都想笑:他过去抗拒 test-first 是因为浪费的是自己的时间,但让 agent 做就很好——因为浪费的是 agent 的时间。

这句话不是开玩笑,它是对 TDD 这个老话题的一次“agent 时代再发明”。

TDD 对 agent 还有一个独特价值:它防止过度实现。

agent 最大的毛病之一是太热情。你让它写一个简单功能,它会顺手给你加一个策略模式、一个工厂模式、再来一个观察者模式套着。这种“AI 架构师综合症”在没有约束的场景下几乎必然发生。

但你一旦把任务变成“让这个失败测试通过”,agent 的行为就被收紧了。它不再追求“漂亮的解决方案”,它追求“让红灯变绿”。这中间的差距是巨大的。

这就是 Simon 的 pattern 化能力:他没有停留在“AI 时代更需要测试”这种抽象判断,他把它压缩成一句能调用模型内部已经训练好的整套 TDD 知识的短 prompt。 包括“先确认测试失败”、“实现只做最小改动”、“绿灯之后再重构”。

他还特别提醒过一件事:测试必须先失败。 如果你跳过红灯阶段,测试可能本来就过得了,那它就没证明任何东西,只是一个装饰品。

这条提醒很多人不当回事。但它恰恰是 TDD 和“凑测试覆盖率”之间唯一的分界线。一个 TDD 写出来的测试,第一次跑必然是红的;一个“为了凑覆盖率写的”测试,第一次跑大概率就是绿的——后者证明不了任何业务行为。


八、Pattern 三:Manual testing——亲眼看见这件事不能省

聊到这里,我必须把一个特别重要的 pattern 单独拎出来讲:manual testing。

我有个朋友(真的,不是上一个朋友),他听我说“agent 能写测试、能跑测试”,立马得出一个结论:“那 manual testing 是不是就可以省了?”

我说:“正好相反。”

他不信。我请他在我笔记本上演示一下他最近的 Cursor 工作流。他给 Cursor 讲了一个新功能,Cursor 写了实现、写了测试、跑测试、全绿。他得意地说:“你看,没问题啊。”

我说:“打开浏览器试一下这个功能。”

他打开了。点击新加的按钮。页面卡住了。控制台报了个红——一个跟新功能无关的旧函数被 agent 顺手“优化”过了。

他愣住。“测试怎么没抓到?”

我说:“因为测试只测了这个新功能。它没测整体 UI,没测真实用户路径,没测浏览器渲染——除了它自己写的那几个 case,什么都没测。”

这就是 Simon 在《Your job is to deliver code you have proven to work》里反复强调的事:证明代码能工作有两个步骤,而且都不是可选项——手动测试和自动化测试。

为什么手动测试是必做的?因为自动测试通过 ≠ 软件能用。

举个例子。某团队改了一个登录接口,单元测试全绿,集成测试全绿,CI 亮着大绿灯。结果上线后用户登不进去——因为测试用的是 mock 数据库,真实数据库的字段名跟 fixture 里的不一样。这种事在 AI 时代会变多,因为 agent 特别擅长“在它搭好的测试路径上把测试搞绿”,但它不一定知道真实环境里那些字段是怎么命名的。

或者更隐蔽的:一个 UI 组件改了样式,snapshot 测试通过,因为它只验证 HTML 结构没变。但实际打开页面,因为 CSS 层级冲突,关键按钮被遮住了。agent 不会“打开页面看一眼”,它只会“跑测试”。

自动测试和手动测试覆盖的是不同类型的风险——

  • 自动测试覆盖“我已经知道要验证什么”——你写过测试,行为预期已经固化了。
  • 手动测试覆盖“我还不知道有什么问题”——你打开真实系统,看到没预期到的状态、报错、UI 异常。

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 的“原料厂”。每一次手动测试发现的问题,都被沉淀成长期的自动化资产。

这才是符合工程师品味的做法:不是把两种测试当“二选一”,而是让它们互相喂养。


九、Pattern 四:Show your work——把“我测试过了”变成“这是证据”

接下来这条 pattern,是 Simon 个人风格最浓的部分,也是我个人最喜欢的部分:Show your work——让 agent 把自己干的事亮出来。

为什么重要?因为 agent 最危险的一种“幻觉”,不是“代码写错了”——代码写错了,跑测试就会发现。最危险的是 agent 告诉你“我测试过了,没问题”,但它其实没真的测,而是根据预期编出来的结果。

Simon 自己见过这种事。他做了一个工具叫Showboat——你可以理解为一个“agent 行为录像机”。核心机制很简单:让 agent 在测试过程中构建一个 Markdown 文档,记录它执行了什么命令、得到了什么输出、看到了什么截图、验证了什么行为。每一项都是真实命令真实输出,不是 agent 自我陈述。

而且 Simon 还专门防了一招——他注意到 agent 有时候会直接编辑 Markdown 文件、伪造结果,而不是真去跑命令。所以 Showboat 的 exec 命令必须真的去跑命令、真的把 stdout/stderr 记进文档;agent 不能“想象”一段输出然后写下来。

这件事的工程含义比工具本身更深:在 AI 时代,code review 不再只审代码,还要审证据。

我在 Fog Creek 做 code review 的时候,看的主要是代码——这一行写得对不对、命名规不规范、有没有边界 bug、性能行不行。但今天这一套不够了。原因很简单——AI 可以在十分钟里改五十处代码,你来不及一行行看;AI 写的代码通常表面上很合规,因为它读过很多优秀代码,知道“看起来怎样像好代码”;真正的问题往往不在代码本身,而在“这段代码到底有没有真的被执行过、真的覆盖了用户路径”。

这三条加在一起,意味着你必须把审查重心,从“代码本身”挪一部分到“行为证据”上。

什么是行为证据?一段真实的命令 + 真实的输出;一张真实的截图 + 真实的页面状态;一份真实的 API 请求 + 真实的响应;一组真实的测试运行日志 + 真实的耗时和结果。这些东西 agent 都可以生成,也是 Showboat、Rodney 这类工具被设计出来的目的。

Simon 在这里做的事,是把“我亲眼看过它运行”这个主观声明,变成了可复核的工件。

这是工程师面对 AI 输出的中间道路——不是盲信模型,也不是每次都像审计一样读完每一行代码,而是用测试、演示、证据、可回滚机制建立信任。

我特别想强调:这是 code review 在 AI 时代必须发生的最重要变化之一。哪个团队最先把 review 流程升级到“既审代码也审证据”,哪个团队就能在 AI 编程的浪潮里建立起真正的质量护城河。


十、Pattern 五:让 agent 模仿好习惯——代码库本身就是最大的 prompt

Simon 有一条特别现实的观察:LLM 会奖励优秀的工程实践。

他举过一个很接地气的例子:哪怕你的代码库里只有一两个你自己喜欢的测试样式,agent 也会照着写。如果代码库整体高质量,agent 通常也会按高质量的方式增量;如果到处是脏活和反模式,agent 就会继续复制脏活和反模式。

他甚至说过,他不太喜欢“写 AGENTS.md 逐条告诉 agent 怎么写代码”这种思路——更高杠杆的做法,是把整个项目本身做成一个 agent 能学到好风格的地方

道理很简单:显式规则的容量是有限的,隐性风格可以无限扩展。 一份 AGENTS.md,再勤奋也就几页纸。但你的代码库可能有几十万行——几千个测试、几百个模块、上百份文档、几年的 Git 历史。这些东西 agent 全都能读、全都会模仿、全都会沉淀进它的工作策略。你的代码库本身,就是给 agent 的最大一段 prompt。

所以 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 羞于写脏代码的地方。

这条原则的方向是反的——它要求你先把过去欠的工程债还掉。如果你的项目没有测试、没有文档、没有规范、没有 CI,那么 AI 时代你不仅不会受益,反而会受害。因为 agent 会以更快的速度,把混乱再扩张一遍。

AI 编程时代,过去的工程债会以更高的利息被结算。


十一、Pattern 六:用 Git 管理 agent 的速度与风险

我在 Fog Creek 那时候就有一个观察:一个团队对 Git 的熟练度,几乎能直接预测它的工程成熟度。

Simon 在 agent 时代,把这条规律推到了新高度。他几乎把 Git 看作和 coding agent 合作的关键工具。

新 session 用“Review changes made today”把 agent 拉进上下文。 这一句很短,但效果惊人。让 agent 先扫今天的 commit log,它就会把“最近改了什么”作为后续动作的基础——就像新人接手任务前先看 Git log + PR 描述。Simon 说的没错,agent 通常非常懂 Git,log、branch、reflog、bisect 都用得很熟。

每一个 agent task 都从干净分支开始。 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 一:把未审查代码丢给别人

讲完六条 pattern,得讲反模式。先讲 Simon 最痛恨的那一条。

Simon 反复反对的一种做法:把 agent 生成的大量代码未经自己审查就提交 PR,让同事或开源 maintainer 替你收拾。

他说这种行为“非常常见,也非常令人沮丧”。如果你提交几百甚至几千行 agent 生成的代码,却没有确认它真的能工作,你其实是在把真正的工作委派给别人。

这条反模式的本质不是“用了 AI”,而是“逃避责任”。

逻辑很简单:你的同事自己也可以用 agent。那你的价值在哪?在于理解问题、设计方案、约束 agent、验证结果、清理实现、补上测试、解释取舍、给 reviewer 足够的上下文。如果你只是把 agent 的输出转发给别人——你不是在用 AI 提高生产力,你是在用 AI 制造团队成本。

说再直接一点:用 agent 写大量代码再不审就提 PR 的人,正在系统性地伤害团队。 他自己不审,意味着 reviewer 要审;reviewer 要审一段连作者本人都没确认过的代码,难度翻好几倍——因为 reviewer 没有上下文,不知道哪里是改动核心,不知道哪里有过权衡,不知道哪里被验证过。

更糟的是,这种 PR 会让团队的 review 文化整体退化。资深工程师发现“PR 里塞一堆未审的 agent 代码会浪费时间”,开始拒绝 review 新人的 PR,新人因此得不到反馈,就更不会成长。一个团队一旦把 agent 当甩锅工具,整个工程师培养机制都会崩。

Simon 提出的“好的 agentic engineering PR”标准很清楚——

  1. 代码能工作,而且你有信心它能工作。 不是“测试好像过了”,是“我亲眼看过它跑过,我知道它的边界”。
  2. 改动足够小、可 review。 一个 PR 一个意图。不要把 agent 三天的输出一次提交。
  3. 附带额外上下文。 上层目标、相关 issue、设计取舍——告诉 reviewer 你为什么改、改到哪一步、哪些是被刻意保留的。
  4. agent 写的 PR 描述也要审。 让别人读你自己都没读过的文字,是一种新的不礼貌。

我建议任何严肃团队都把它写进协作规范——所有 AI 辅助的 PR,必须附带三类证据:自动化测试结果、手动测试说明、作者对关键实现的解释。 这样 AI 就不再是隐藏在背后的“神秘生产力”,它会进入可审查、可追责、可复盘的工程流程。


十三、Anti-pattern 二:不写测试,或者把测试当装饰

Simon 对“不写测试”的态度,这一两年越来越硬。

他原话之一是:现在还有人用 coding agent 写代码却完全不写测试,这是非常糟糕的想法。过去不写测试的理由是测试本身有维护成本——但在 agent 时代,测试几乎免费——agent 能在几分钟里整理出一套像样的测试——因此再不写测试,纯粹就是工程偷懒。

但他同样警告:测试装饰化也是一个严重问题。

什么是测试装饰化?就是测试存在的目的不是验证实现,而是让 PR 看起来专业。识别特征——

  • 测试用例多但覆盖路径浅;
  • assert 大量用 assert result is not Noneassert 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 三:自动测试全绿就等于交付完成

第三个反模式,在第八节已经铺垫过:自动测试不能替代 manual testing。

这里不再重复论证——核心道理就一个:agent 写测试的时候,很容易写出“覆盖自己实现路径”的测试,但漏掉真实用户路径。

假设你让 agent 改购物车的优惠券逻辑。agent 写了实现,又写了测试,覆盖了它理解的边界条件和代码分支。但真实用户怎么用?从首页加入购物车、跳转、点“使用优惠券”、选了一张券、看到折扣金额——整套行为可能涉及前后端各五个组件、三个接口、两个数据库表。agent 的测试大概只能覆盖其中一两块。

测试全绿 ≠ 用户能用。

Simon 推荐的不是“更多单元测试”,而是多层验证:单元测试证明局部逻辑,集成测试证明跨模块路径,manual testing 证明真实行为,浏览器自动化(Playwright/Rodney)证明 UI,Showboat 文档证明过程,截图和录屏证明结果。不同证据覆盖不同风险。

我最近在一个团队里推了一条规则:任何涉及用户可见行为的 PR,必须附带至少一个真实交互证据——一段 curl 输出、一张截图、一段 Playwright 的 trace 文件。不是测试结果,是真实交互。规则上线之后,团队线上事故下降非常明显。

原因不是工程师变聪明了,而是大家被迫把“真实运行一次”变成了 PR 的硬性步骤。绝大多数线上事故,本来就不是因为工程师不聪明,而是因为大家省略了“真实运行一次”。


十五、Anti-pattern 四:YOLO mode 没有安全边界

Simon 并不反对 YOLO mode——也就是放手让 agent 去跑各种命令、不每一步都要批准。他承认 YOLO mode 有非常大的生产力价值,因为不断请求人工批准会显著降低 agent 通过反复尝试解决问题的能力

但他列了很实在的风险:agent 可能做出糟糕决策;可能受 prompt injection 攻击;最强大的工具往往是“在 shell 里执行命令”,一个失控的 agent 可以做很多人类用命令也能做的坏事;错误的 shell 命令可以破坏文件系统;攻击者可以通过 prompt injection 让 agent 泄露源码、环境变量、密钥;你的机器甚至可能被当作攻击代理。

我看到很多团队在这一块毫无防备——让 agent 直接接触生产环境的 credential、直接读取真实用户数据、直接连接生产数据库。没出事之前看着没事,一旦出事就是灾难级的。

Simon 的解法仍然是 pattern 化——

  • 想放开 agent,先放进 sandbox。 容器、虚拟机、Codespaces——别让它在你的本机直接乱跑。
  • credential 最小权限。 给 agent 的是只读的数据库账号、只能访问测试桶的存储 key、只能看分析数据的 BI 账号。
  • 如果 credential 能花钱,就设预算上限。 Cloud key、API key、模型调用 key——所有“花钱的”都设 cap。YOLO mode + 没有预算上限 = 可能产生几千上万美元的事故。
  • 尽量用 test/staging 数据,不用生产数据。

Simon 还反对一种更隐蔽的做法:拿敏感生产数据做测试。 他建议投资 good mocking——一键创建随机用户、为特殊 edge case 创建模拟用户、为不同角色创建不同的 fixture。

这个行业过去十几年,“用生产数据做测试”是被默许甚至鼓励的——理由是“只有真实数据才能测出真实问题”。但 agent 时代这条路走不通了。agent 的访问粒度比人粗、受 prompt injection 影响、可以被“诱导”外泄数据、操作日志比人类难追溯——四条加起来,生产数据 + agent = 高风险组合。谁还在这么干,就是在赌运气。

Simon 在这里的思维体现得很清楚:他不是简单说“YOLO mode 危险,不要用”——他承认 YOLO mode 的生产力价值,然后给你列具体的隔离机制。 不是禁止能力,而是给能力套上边界。这才是工程纪律该有的姿态。


十六、Pattern 七:Conformance-driven development——用多个实现反推出规范

Simon 还有一个特别有启发性的实践:conformance-driven development。

他给 Datasette 加 multipart file uploads 的时候,干了一件事:让 Claude 构建一个“文件上传”的测试套件,要求这套测试在多个已有框架(Go、Node.js、Django、Starlette 等)上都能跑过。然后再用这套测试去驱动 Datasette 的实现。

他自己的原话是:“像是从六个已有实现反向工程出一个标准,再实现这个标准。”

过去写一个 conformance suite 很费时——你要研究多个实现、抽象共同约束、写大量测试用例。这种活通常是 W3C、IETF 这种标准组织在做,普通工程师没时间也没动力做。

但现在不一样。agent 可以把这种活做得快得多。 它能把多个实现下载下来、跑一遍、抽出共同行为、写出测试套件。人类的价值在于:选择参考实现、判断哪些行为属于规范、哪些只是偶然差异。

这是 agent 时代一个很特别的工程能力——把“模糊需求”转成“可执行规格”。

我把这种能力连同前面几种 pattern 拆成几种典型用法:

  1. TDD: 把单个功能转成失败测试。适合新功能。
  2. Conformance-driven: 把多个现实实现转成测试套件。适合替代实现、兼容层、协议适配。
  3. Manual-derived testing: 把用户行为转成命令和截图。适合面向终端用户的产品。
  4. Showboat documentation: 把测试过程转成证据文档。适合高合规要求的项目。

它们的共同点:把“工程师脑子里那种模糊的‘我希望系统怎么工作’”,转成 agent 能执行、能验证、能复用的具体工件。

这就是 Simon 的真正贡献——他不是教你怎么用 AI 写代码,他是教你怎么把抽象工程经验沉淀成可调度的执行单元。


十七、Simon 的组织启示:AI 时代更需要 senior engineering

讲到这里,有一件特别违反直觉的事需要说:AI 编程时代,对 senior engineering 的需求是上升的,不是下降的。

很多人担心 AI 会让初级工程师“被掏空”——既然 agent 能写代码,那初级工程师做什么?

这种担忧有道理,但 Simon 的视角不一样。他在 Pragmatic Summit 的炉边谈话里讲过:同时驱动多个 agent 是非常耗脑的。 你需要不断切换项目、审查输出、给反馈、决定下一步、做权衡、设计验证、发现遗漏。这不是“靠 AI 偷懒”,这是要求你全力运转。

在《Vibe engineering》里,他把“会用 AI 的工程师”日常画得更清楚——研究方案、决定架构、写 specification、定义成功标准、设计 agentic loops、规划 QA、管理一群“数字实习生”、做大量 code review。

这些活,逐条拆开看,几乎都是 senior engineer 的特征。

所以在 Simon 的观察里,AI 编程不是降低了工程标准,而是把瓶颈从“你能不能写代码”转移到了“你能不能管好代码”。你能不能清楚定义任务?能不能提供足够上下文?能不能判断结果对错?能不能发现边界问题?能不能让 agent 证明它做对了?能不能把这一次的经验沉淀成下一次可复用的 prompt、测试、脚本或文档?

这套问题,全是 senior 工程师才有能力答的。AI 让“敲键盘”贬值,但让“判断力”升值。

Simon 还提到一个我特别喜欢的概念:compound engineering loop。 意思是——每次 agent session 结束后,把有效的经验沉淀下来,更新项目的 README、AGENTS.md、测试模板、工具脚本、流程文档,让下一次 agent 运行得更好。

AI 不会自己从过去的错误里学习,但你的代码库、你的文档、你的测试、你的工具链,可以。

一个团队的 agentic engineering 成熟度,就反映在“compound engineering”做得有多好——这些可累积资产是不是越来越厚、越来越对、越来越能让新 agent 即用即上。谁最先建起这种 compound engineering loop,谁就在新时代里建立了真正的代差。


十八、把 Simon 这套整理成一份可执行的工程清单

把 Simon 这一整套压缩成可立刻上手的清单,大致八步。我用工程师本位的语气讲,希望你直接抄走——

第一,开始之前先准备环境。 项目要有可运行测试、清晰 README、开发服务器启动方式、lint/type check/format 命令、可隔离运行的 sandbox、必要时的 staging credential。agent 不是魔法,它需要工具和边界。跳过这一步,后面的所有努力都会被环境的脏乱抹平。

第二,新 session 先让 agent 进入上下文。 让它先跑测试,看 Git 最近变化,读相关测试,必要时用 subagent 探索代码库。不要一上来就让它写代码;先让它知道自己站在哪里。

第三,新功能用 red/green TDD。 先写失败测试,再写实现,让测试变绿。测试必须先失败,红灯阶段不能跳过。

第四,测试通过后做 manual testing。 库函数用 python -c 或临时 demo 文件;API 用 curl;Web UI 用 Playwright、Rodney 或浏览器自动化;需要视觉判断时让 agent 截图自己检查。自动测试不是“亲眼看见”。

第五,让 agent 留证据。 用 Showboat 或类似机制记录命令、输出、截图和说明。把“测试过”从主观声明变成可审查材料。

第六,把发现的问题固化为测试。 manual testing 发现 bug,不仅让 agent 修,还要让它用 red/green TDD 写进回归测试。每一个被人类发现的问题,都应该变成一个永远不会被同一个 bug 再咬到的自动化资产。

第七,提交前自己 review。 不要把 agent 输出原封不动丢给别人。PR 要小、可解释、有上下文、有测试证据、有手动验证说明。agent 写的 PR 描述也要审——让别人读你自己都没读过的文字,是新一代的不专业。

第八,复盘并沉淀。 把有效的 prompt、测试模式、工具说明、失败经验、mock 数据生成方法写进项目,让下一次 agent 更容易做对。AI 不会从过去学习,但你的代码库可以——这就是 compound engineering loop。

这八步加起来,差不多就是一个团队从“用 AI”升级到“用 AI 做工程”的最小路径。每一条都不复杂,每一条都很贵——贵的不是技术成本,是工程师改变习惯的成本。但谁先建立这套习惯,谁就在 AI 时代有真正的杠杆。


十九、回到那个朋友的故事

写到这里,我想回到文章开头那个朋友。

他后来在电话里说:“我们团队这一年慢慢把 Simon 那套 patterns 揉进工作流。修 bug 的时间,回到正常了。”

我问他:“是哪一条最有用?”

他想了一下,说了一个我意料之外的答案:“最有用的不是某一条 pattern,是‘不要把没审过的代码扔给同事’这条 anti-pattern。”

他解释说,团队过去半年的真正改变,不是从某天起开始用 red/green TDD,也不是从某天起开始用 Showboat——而是从某天起,review 通过的隐性门槛变了

过去:测试绿了 + 你看着没问题,就 merge。

现在:测试绿了 + 你手动跑过 + 你给出真实交互证据 + 你能解释关键实现,才 merge。

光这一个改变,整个团队的代码质量就回到了 AI 到来之前的水平——而且因为 agent 的速度,产出还是过去的两倍。

我问他:“那你们现在 Cursor 用得还多吗?”

他说:“比以前还多。但不一样了——以前我们让 Cursor 替我们干活,现在我们让 Cursor 替我们打草稿。最后的判断、验证、整理,都还是我们的。”

我笑了。“恭喜你,你升级成了一个 agentic engineer。”

电话那头他也笑了:“我觉得你应该感谢的是 Simon。”

是的。我也这么想。


二十、结语:把 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 代表的是下一阶段——“现在我们该如何证明这些代码值得交付?”

这句话听上去保守,其实很深。它把焦点从“产能”挪回了“交付”——从“我们能写多少”挪回了“我们能稳定交付多少”。这是任何一个真正经历过软件工程长期周期的人,都会本能认同的视角。

我在 Fog Creek 的时候有一句口头禅:“软件不是写完就行的,软件是一直要工作的。”这句话十几年没变过。Simon 用一组 agentic engineering patterns,把它翻译进了 AI 时代。

AI 让写代码的成本下降了,但软件工程从来不只是写代码。真正稀缺的,是知道该写什么、怎样证明它工作、如何让别人安全地接手、如何让系统在未来继续可维护。

这些事情,Simon 在用一组小而具体的 pattern 一件件地教给我们。

他不教大道理,他教暗号。

下一次你打开 Cursor、Codex、Claude Code,进入一个新 session,记得先打这五个字——

First run the tests.

这就是 Simon 想要你养成的肌肉记忆。

把这条记下,把这条做实,剩下的整套 agentic engineering,都会自然长出来。

至于愿不愿意把它做实——那就是你的选择了。

但请记得:软件不是写完就行的,软件是一直要工作的。