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”标准很清楚——
- 代码能工作,而且你有信心它能工作。 不是“测试好像过了”,是“我亲眼看过它跑过,我知道它的边界”。
- 改动足够小、可 review。 一个 PR 一个意图。不要把 agent 三天的输出一次提交。
- 附带额外上下文。 上层目标、相关 issue、设计取舍——告诉 reviewer 你为什么改、改到哪一步、哪些是被刻意保留的。
- agent 写的 PR 描述也要审。 让别人读你自己都没读过的文字,是一种新的不礼貌。
我建议任何严肃团队都把它写进协作规范——所有 AI 辅助的 PR,必须附带三类证据:自动化测试结果、手动测试说明、作者对关键实现的解释。 这样 AI 就不再是隐藏在背后的“神秘生产力”,它会进入可审查、可追责、可复盘的工程流程。
十三、Anti-pattern 二:不写测试,或者把测试当装饰
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 三:自动测试全绿就等于交付完成
第三个反模式,在第八节已经铺垫过:自动测试不能替代 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 拆成几种典型用法:
- TDD: 把单个功能转成失败测试。适合新功能。
- Conformance-driven: 把多个现实实现转成测试套件。适合替代实现、兼容层、协议适配。
- Manual-derived testing: 把用户行为转成命令和截图。适合面向终端用户的产品。
- 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,都会自然长出来。
至于愿不愿意把它做实——那就是你的选择了。
但请记得:软件不是写完就行的,软件是一直要工作的。