Simon Willison教我的事:你交付的不是代码,是被你证明过的代码
风格参考:Joel Spolsky —— 老牌程序员博客腔,故事开场,幽默类比,可读性优先。立场:拥护。
一、那个让我从此对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在这里的判断我认为是这一年最有分量的工程判断之一:“敲代码便宜了”≠“交付好代码便宜了”。 因为“好代码”的标准没有因此变松。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只能在里面快速、不稳定、不可验证地搞破坏。
第二,“代码库即prompt”。你的代码库本身,就是给agent的最大的一段prompt。你不用写在AGENTS.md里,agent扫一眼代码就明白了。 所以,如果你想让agent帮你写出好代码,第一步永远是先把你的代码库变成一个能让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发现项目的测试套件。 它得自己去找怎么跑测试,可能是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。它是一个工程暗号,触发的是模型已经理解的整套行为模式。
我特别欣赏这种“把工程文化中的隐性规矩,变成可调用的几个词”的能力。这是过去很多团队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花两分钟先写一个失败测试再写实现,对你来说几乎没有任何额外心理负担——浪费的不是你的时间,是agent的时间。
Simon有一句话我每次想到都觉得有点想笑:他过去抗拒test-first是因为浪费的是自己的时间,但让agent做这件事就很好,因为浪费的是agent的时间。
这句话不是开玩笑,它是对TDD这个老话题的一次“agent时代再发明”。
更重要的是,TDD对agent还有一个独特的、人类时代没有的价值:它防止过度实现。
agent最大的毛病之一是太热情。你让它写一个简单功能,它会顺手给你加一个策略模式、一个工厂模式、再来一个观察者模式套着。这种“AI架构师综合症”在没有约束的场景下几乎是必然发生的。
但你一旦把任务变成“让这个失败测试通过”,整个agent的行为模式就会被收紧。它不再追求“漂亮的解决方案”,它追求“让红灯变绿”。这中间的差距是巨大的。
这就是Simon的pattern化能力:他没有停留在“AI时代我们更需要测试”这种抽象判断,他把它压缩成一句短prompt。 这一句话能调用模型内部已经训练好的整套TDD知识——包括“先确认测试失败”、“实现只做最小改动”、“绿灯之后再重构”。
而且他特别提醒过一件细节:测试必须先失败。 如果你跳过红灯阶段,测试可能本来就过得了,那它就没证明任何东西,只是一个装饰品。
这条提醒很多人不当回事。但实际上它是TDD和“凑测试覆盖率”之间唯一的分界线。一个TDD写出来的测试,第一次跑必然是红的;一个“为了凑覆盖率写的”测试,第一次跑大概率就是绿的——而后者证明不了任何业务行为。
八、Pattern 三:Manual testing——亲眼看见这件事不能省
聊到这里,我必须把一个特别重要、但很多人会下意识忽略的pattern单独拎出来讲:manual testing。
我有个朋友(真的,不是上一个朋友),他听我说“agent能写测试,能跑测试”,立马得出一个结论:“那manual testing是不是就可以省了?”
我说:“正好相反。”
他不信。
我请他在我笔记本上演示一下他最近的一段Cursor工作流。他给Cursor讲了一个新功能,Cursor写了实现,写了测试,跑测试,全绿。他得意地说:“你看,没问题啊。”
我说:“那你打开浏览器试一下这个功能。”
他打开了。点击新加的按钮。页面卡住了。控制台报了个红——一个跟新功能无关的旧函数被它顺手“优化”过了。
他愣住。“这……这测试怎么没抓到?”
我说:“因为测试只测了这个新功能。它没测整体UI。它没测真实用户路径。它没测浏览器渲染。它什么都没测,除了它自己写的那几个case。”
这就是Simon在《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发现的问题,都被沉淀成长期的自动化资产。
这是真正符合工程师品味的做法:不是把两种测试当成“二选一”,而是让它们互相喂养。
九、Pattern 四:Show your work——把“我测试过了”变成“这是证据”
接下来这一条pattern,是Simon个人风格最浓的部分,也是我个人最喜欢的部分:Show your work——让agent把自己干的事亮出来。
为什么这件事重要?因为agent最危险的一种“幻觉”,不是“代码写错了”——代码写错了,你跑测试就会发现。最危险的一种是agent告诉你“我测试过了,没问题”,但它其实没真的测,它是根据预期编出来的结果。
Simon自己见过这种事。他做了一个工具叫Showboat——你可以理解为一个“agent行为录像机”。它的核心机制非常简单:让agent在测试过程中构建一个Markdown文档,记录它执行了什么命令、得到了什么输出、看到了什么截图、验证了什么行为。每一项都是真实命令真实输出,不是agent自我陈述。
你以为这就完了?没完。Simon在做这个工具时还专门防了一招——他注意到agent有时候会直接编辑Markdown demo文件、伪造结果,而不是真去跑命令。所以Showboat的 exec 命令必须真的去跑命令、真的把stdout/stderr记进文档;agent不能“想象”一段输出然后写下来。
这件事的工程含义比工具本身更深。它告诉我们一个判断:在AI时代,code review不再只审代码,还要审证据。
我在Fog Creek做code review的时候,看的主要是代码——这一行写得对不对、命名规不规范、有没有边界bug、性能行不行。但今天,这一套方法已经压不过来了。原因很简单——
- AI可以在十分钟里改五十处代码——你来不及一行行看;
- AI写的代码通常表面上很合规——它读过很多优秀代码,它知道“看起来怎样像是好代码”;
- 真正的问题往往不在代码本身,而在“这段代码到底有没有真的被执行过、真的覆盖了用户路径”。
这三条加在一起,意味着你必须把审查重心,从“代码本身”挪一部分到“行为证据”。
什么是行为证据?
- 一段真实的命令 + 真实的输出;
- 一张真实的截图 + 真实的页面状态;
- 一段真实的录屏 + 真实的交互流程;
- 一份真实的API请求 + 真实的响应;
- 一组真实的测试运行日志 + 真实的耗时和结果。
这些东西都是agent可以生成的,也是Showboat、Rodney这类工具被设计出来的目的。
Simon在这里做的事,是把“我亲眼看过它运行”这件事,从主观声明变成了可复核的工件。
这就是工程师面对AI输出的中间道路——不是盲信模型,也不是每次都像审计一样读完每一行代码,而是用测试、演示、证据、可回滚机制建立信任。
我特别想替Simon强调一下:这是code review在AI时代必须发生的最重要变化之一。哪个团队最先把code review的SOP升级到“既审代码也审证据”,哪个团队就能在AI编程的浪潮里建立起真正的质量护城河。
十、Pattern 五:让agent模仿好习惯——把“代码库风格”当成隐性 prompt
Simon有一条特别现实的观察:LLM会奖励优秀的工程实践。
什么意思呢?他举过一个非常接地气的例子:哪怕你的代码库里只有一两个你自己喜欢的测试样式,agent也会照着写。如果代码库整体高质量,agent通常也会按高质量的方式增量;如果代码库到处是脏活和反模式,agent就会继续复制脏活和反模式。
Simon甚至说过,他不太喜欢“写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 六:用 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生成的代码,却没有确认它真的能工作,你其实是在把真正的工作委派给别人。
我必须替Simon再补一刀,因为这一刀实在太重要——
这条反模式的本质不是“用了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 二:不写测试,或者把测试当装饰
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 的替代品
第三个反模式我已经在前面铺垫过:自动测试不能替代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 四: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数据,不用生产数据。
Simon还反对一种更隐蔽的做法:拿敏感生产数据做测试。 他建议投资good mocking——一键创建随机用户、为特殊edge case创建模拟用户、为不同角色创建不同的fixture。
我特别同意这条建议。我们这个行业过去十几年,“用生产数据做测试”是被默许甚至鼓励的——理由是“只有真实数据才能测出真实问题”。但agent时代这条做法完全不能继续了。原因是——
- agent的访问粒度比人粗;
- agent受prompt injection影响;
- agent可以被“诱导”把数据外泄;
- agent的操作日志比人类难追溯。
四条加起来,意味着生产数据 + agent = 一个高风险组合。哪个团队还在这么干,就是在赌运气。
Simon在这里的pattern思维体现得非常清楚:他不是简单说“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时代一个非常特别的工程能力——它能把“模糊需求”转成“可执行规格”。
我把这种能力拆成几种典型用法:
- 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还提到一个我特别喜欢的概念:compound engineering loop。 它的意思是——每次agent session结束之后,把这次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探索代码库。不要一上来就让它写代码;先让它知道自己站在哪里。 “First run the tests”和“Review changes made today”加起来,能让你少踩很多坑。
第三,新功能用 red/green TDD。
先写失败测试,再写实现,让测试变绿。测试必须先失败,红灯阶段不能跳过。 这一点要写进团队规范,不写就会被略过。
第四,测试通过后做 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那套patterns揉进工作流。修bug的时间,回到正常了。”
我问他:“是哪一条最有用?”
他想了一下,说了一个我意料之外的答案:“最有用的不是某一条pattern,是‘不要把没审过的代码扔给同事’这条 anti-pattern。”
他解释说,他们团队过去半年的真正改变,不是从某天起开始用red/green TDD,也不是从某天起开始用Showboat——而是从某天起,review通过的隐性门槛变了。
过去:测试绿了 + 你看着没问题,就merge。
现在:测试绿了 + 你手动跑过 + 你给出真实交互证据 + 你能解释关键实现,才merge。
光这一个改变,整个团队的代码质量就回到了AI到来之前的水平——而且因为agent的速度,他们的产出还是过去的两倍。
我问他:“那你们现在的Cursor用得还多吗?”
他说:“比以前还多。但不一样了——以前我们让Cursor替我们干活,现在我们让Cursor替我们打草稿。然后所有最后的判断、验证、整理,都还是我们的。”
我笑了。“恭喜你,你升级成了一个agentic engineer。”
电话那头他也笑了:“我觉得你应该感谢的是Simon。”
是的。我也是这么想的。
二十、结语:把 AI 编程拉回了软件工程,这就是 Simon 的真正贡献
讲到这里,可以收尾了。
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,都会自然长出来。
至于愿不愿意把它做实——那就是你的选择了。
但请记得:软件不是写完就行的,软件是一直要工作的。