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

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”标准非常清楚——

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

这套标准非常适合制度化。我建议任何严肃团队都把它刻进协作规范——所有AI生成或AI辅助的PR,必须附带三类证据:自动化测试结果、手动测试说明、作者对关键实现的解释。

这样AI就不再是隐藏在背后的“神秘生产力”,它会进入可审查、可追责、可复盘的工程流程。一个团队真正成熟的标志,就是它能把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 的替代品

第三个反模式我已经在前面铺垫过:自动测试不能替代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时代一个非常特别的工程能力——它能把“模糊需求”转成“可执行规格”。

我把这种能力拆成几种典型用法:

  1. TDD:把单个功能转成失败测试。 适合做新功能。
  2. Conformance-driven:把多个现实实现转成测试套件。 适合做替代实现、做兼容层、做协议适配。
  3. Manual-derived testing:把用户行为转成命令和截图。 适合做面向终端用户的产品。
  4. 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,都会自然长出来。

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

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