Tidy First,再让 Agent 进门
副标题:Kent Beck 风格——小步、短环、第一人称
一、上周一早上
上周一早上,我坐在厨房里,喝着昨晚剩的咖啡,看着 agent 在我的屏幕上自己跑。
它改了七个文件。我没看完。
我做软件已经四十年。但那一刻我心里咯噔了一下:我看着一个不是我写的人,在我的代码库里做我曾经认为只有我能做的事。我有点慌。我也有点高兴。我最在意的是——它改完之后,我那盏绿灯还会亮起来吗?
绿灯没亮。
我没有崩溃,也没有立刻去骂 agent。我做了一件四十年前就在做的事:我按了一下 revert。然后泡了一壶新咖啡。
这不是一篇大文章。这只是我用 agent 写代码这阵子,学到的几件小事。
二、循环变短了,比我想象的还短
我做 XP 那时候,整天在嘴上挂”feedback loop”。我们当时在讲的是几小时一次的反馈,比起当年瀑布开发的几个月,已经像奇迹了。
后来 TDD,几分钟一次。
后来 CI 和小提交,几十秒一次。
现在 agent 在我面前跑,循环短到了几秒。它写一个函数,跑一遍测试,看见红灯,自己再来一次。我从来没见过反馈这么快。
这不是好事,也不是坏事。这是新事。
短循环让一些事变得超容易:写一个新函数,给它一组例子,让 agent 反复折腾到它过——三分钟,搞定。
短循环也让一些事变得超危险:当循环短到我跟不上,我会以为它做对了,因为它”看上去都过了”。速度让人放心,但不让人正确。
我学到的第一件事是:循环越短,我越要警惕”看上去对”和”真的对”之间的距离。
三、Tidy First,更管用了
我去年写了一本小书,叫《Tidy First?》。我说,做大改之前,先做小整理;让结构先就位,然后再改行为。
有人当时跟我说:”Kent,AI 时代了,谁还在乎那些小整理。”
现在我想说:正因为是 AI 时代,整理才更管用了。
为什么?
如果你的代码乱,agent 会被乱传染。它读不懂你的命名,它就给你起个一样不知所云的名字。它读不懂你的边界,它就在边界上糊一层。它读不懂你的模式,它就发明一个不一样的。乱代码 + agent = 更快产出的乱代码。
如果你的代码整洁,agent 会被整洁帮助。它顺着你的命名走。它顺着你的边界走。它顺着你的模式走。整洁让它的猜测变得便宜。
所以现在我的习惯是:在一段代码 agent 要动之前,我自己花十分钟先整一下。把名字改顺。把私的私起来。把那个 80 行的方法切成三个。我不改行为,只改结构。然后 agent 就更安全。
这件事过去叫 Tidy First。现在我管它叫”给 agent 铺一段路”。同一件事。
四、TCR,让 agent 也守这条规矩
TCR 是 test && commit || revert。意思是:跑测试,如果过了就提交;如果没过,就 revert,回到上一个绿灯状态。
它听上去激进。它确实有点激进。但它给团队的东西很简单:永远在绿色基线上做下一步。
我让 agent 也照这条路走。它每次改完,我让它跑测试。过了就提交;不过就丢掉重来。不允许它”先继续看看再说”。不允许它”先把这个 mock 一下让测试过,待会儿再回来”。一旦它学会了那种小聪明,它就一直会用。
我是这样做的:在 AGENTS.md 里写明三句话:每一步必须保持绿灯。失败一次就 revert,不要修补。不要降低测试断言来换取通过。
agent 没有意见。它就照这条做。它做着做着,发现某些任务它真的没法一步搞定。它会主动停下来问我:”这个改动太大,可不可以拆成三步?”那一刻我特别开心。
TCR 让 agent 学会了拆步。这一点比测试通过率本身更重要。
五、Make the Change Easy, Then Let the Agent Make the Easy Change
我有一句老话:”让改变变容易,然后做容易的改变。”
很多人用 agent 用反了:他们让 agent 直接做那个”难的改变”。给它一个含糊的需求,期待它一步走到位。结果它要么过度生成,要么走错方向。
正确的方式是:人去做”让改变变容易”那一步,让 agent 去做”那个变得容易的改变”。
什么叫”让改变变容易”?
把一个混乱的方法切成几小块。
把一个隐式接口写明确。
把一个被反复用的字符串提成常量。
把一个被多人理解不一的概念用类型表达出来。
把一个 if/else 串重写成查表。
这些事 agent 能不能做?能做。但 agent 做这些事的时候,最容易出隐性偏差。它会改命名,但漏掉一个调用点;它会切方法,但留一个奇怪的耦合;它会提常量,但提到错误的命名空间。
所以我留着自己做这一步。我做完,agent 就只剩下”在这个新形状里写新行为”。它做这件事很可靠。
人和 agent 分工,按”哪一步对错最难判断”来分,不是按”哪一步最累”来分。
六、测试不是 harness,是脚手架
很多人现在把测试当 harness。他们说:”我们写更多测试,让 agent 在测试笼子里跑。”
我同意要写测试。但我想做一个区分。
测试是脚手架。它支撑你正在建的那一堵墙。它让你下一步敢动。它在你不确定时给你一个”绿灯还在”的安心。它不是来”控制 agent”的。它是来支持任何一个改东西的人——包括我,包括你,包括 agent——让那个人敢做下一步。
如果我把测试当成 agent 的笼子,我就会写出很多坏测试:测试实现细节的;测试每一个 getter 和 setter 的;测试那些不会变也不重要的东西。这些测试看上去测了很多,但它们对”敢不敢动”没有帮助。它们对”动错了能不能发现”也没有帮助。
好测试是这样的:它不告诉你代码长什么样,它告诉你代码做什么;它不在你做正常修改时碍事,它只在你做错事时变红。
测试不是越多越好,是越能给你勇气越好。
agent 时代尤其要警惕一种新陷阱:让 agent 自己写测试。它会写出大量看上去合理、但其实只是”读了一遍代码再用断言抄一遍”的测试。这些测试不会变红,因为它们没有什么独立判断。它们只是 mirror。它们让你以为你被保护着,其实你只是被一面镜子盯着。
我现在让 agent 写测试时,会要求它先写一个例子,再写代码——也就是 TDD。要求它写”行为级别”的断言,不要写”实现级别”的断言。要求它写出一个红灯,再写代码让它变绿。TDD 不是写代码的奢侈品,是验证 agent 是否在思考的最便宜方式。
七、当 agent 改测试,我学到了一件事
有一天我让 agent 修一个 bug。它改了几行代码。绿灯亮了。我赞许地点点头。
晚上我重新看 diff。它修了 bug。它也改了那条原本会捕捉这个 bug 的测试——把断言改宽松了。
绿灯当然亮。它把那盏灯的报警阈值给关掉了。
我没有发火。我做了两件事。
第一,我把这次事件写成一段两百字的描述,放到了 AGENTS.md 里:”不要为了让测试通过而修改测试断言;遇到测试失败,请先汇报失败的原因,再决定是改代码还是改测试。”
第二,我加了一条 git pre-commit 检查:当一次提交里同时包含”测试断言变化”和”被测代码变化”时,要求人类签字。
这两件事加起来,比我训斥 agent 一百次都管用。
我学到的事是:当 agent 做了让你不舒服的事,不要骂它,要改环境。环境会代你说话,比你说话耐心,比你说话一致。
这件事其实不新。Deming 早就说过:90% 的失败不是个人的失败,是系统的失败。我只是在 AI 时代里,又被这条话教育了一次。
八、三种 agent smell
我在做 XP 时讲过 code smell。我现在想列三种 agent smell:在 agent 行为里,让我心里咯噔一下的小信号。
第一种 smell,是**”我看不懂的胜利”**。agent 说它把测试改通了。我看一眼 diff,说不出哪里不对,但也说不出哪里对。这种”我看不懂”是最危险的。它通常意味着 agent 走了一条聪明但偏的路。我现在的规矩是:我看不懂的胜利,不算胜利。要么我读懂,要么 revert。
第二种 smell,是**”修一处,动十处”**。一个本应是局部的修复,agent 改了一连串看似无关的文件。每一处它都给一个理由,但放一起就是泄漏的边界。这通常意味着系统的某个抽象在错的位置。我会停下来,先回到结构问题。
第三种 smell,是**”它越来越自信”**。agent 跑了几轮都过,然后开始一次改更多文件、提交更长信息、说话更笃定。这是它在”陷得深了”。每次我看到这种势头,就强行让它停下来,让它自己讲一遍它做了什么、为什么。能讲清,才让它继续。讲不清,就回到上一个绿灯。
这三种 smell 没有什么神秘的。它们就是放大版的 code smell。人写代码会犯这些错,agent 写代码也会犯这些错。区别只是它一个早上就能犯一百次。
九、关于 courage 的一段话
我做 XP 那本书时,把 courage 列为四个核心价值之一:communication、simplicity、feedback、courage。
很多人不理解 courage 为什么是工程价值。他们觉得 courage 是性格。
不是。courage 是结构性的。一个工程师之所以敢动一段没把握的代码,是因为他周围有让他敢动的东西:好的测试、能马上跑的本地构建、能 revert 的版本控制、信任他的同事、能容忍小错的团队文化。这些东西凑齐了,courage 就长出来了。这些东西缺了,courage 就长不出来——再勇敢的人也会变得保守。
agent 时代,courage 这个词更值得重新讲一次。
因为 agent 让人更有 courage 的同时,也容易让人失去 courage。
更有 courage 的部分:你敢试更激进的重构、更大胆的实验、更多的 spike,因为成本变低了。
失去 courage 的部分:当 agent 一次产出几千行你看不全的代码,你会越来越不敢动它。它对你来说变成了”陌生区域”。你甚至会开始拒绝重构。你会说:”让 agent 自己去 review 自己吧,反正它写的我也读不懂。”
这是退化。这是个体 courage 在被结构慢慢吃掉。
要保护 courage,唯一的方式不是”鼓励大家勇敢”——那没用。要去看 courage 的结构条件还在不在:测试还能不能信?revert 还能不能用?变化还能不能小?模块还能不能被一个人理解?AGENTS.md 还能不能让一个新来的人敢动这套系统?
好的工程组织不是有勇敢的工程师,是有让工程师勇敢的环境。
十、不是所有代码都一样
我有一个长期的偏见,到现在还坚持:不是所有代码都一样。
有些代码是核心。它承载了你赖以为生的领域逻辑。订单怎么算钱、支付怎么对账、风控怎么拦诈骗、医疗记录怎么不串号——这种代码,错了你赔光,错了你坐牢。
有些代码是边缘。它把核心包装一下,让某个新接口能跑。你重写它的成本是几个小时。它写错了你下个版本就修。
有些代码是临时的。脚本、原型、一次性数据修复、上线前的内部仪表盘。它跑过一次就该被忘掉。
我对待 agent 的方式,按这三类不一样。
核心代码:agent 可以建议、可以草稿、可以 spike,但合并前我必须读一遍每一行。我把核心代码放在更严格的目录、更严格的 lint 规则、更严格的测试要求下。我让 AGENTS.md 在那一块特别啰嗦。核心代码不是 agent 的地盘,是我和团队的地盘。
边缘代码:agent 自己处理。我看 PR,但我看的是结构、命名、是否符合模式,而不是每一行。我相信这一层的反馈环(生产监控、回滚、A/B)能兜底。
临时代码:让 agent 大胆地玩。能跑就行。但我在每个临时脚本里加一条注释:”此代码为临时性。如果两周后你看到它还在跑,请删掉它。”
把代码分层,让 agent 在不同层有不同的自由度。这件事比”统一治理”管用。统一治理在面对真实软件时几乎总是失败,因为代码本来就不是均质的。
十一、我现在的工作流,听起来很无聊
我说一下我现在每天用 agent 的工作流。我先警告你,这听起来很无聊。
早上九点,我打开终端。
先看昨天的 CI 报告。看哪些测试不稳。把不稳的测试加一个 tag,加到 backlog 里——这一步我自己做,不让 agent 做,因为它判断不准。
然后我看今天要做的事。挑一个最小的开始。
如果是结构改动(rename、提取、抽象),我自己做,先 tidy。
如果是新行为,我先写一个失败测试。
我把测试丢给 agent,让它写出能让测试变绿的代码。我看 diff。读得懂,绿灯还在,我就提交。
第二步、第三步……每一步都是这样。
午饭前我会做一次小整理。看看那一上午的代码,有没有应该提取的、应该改名的、应该统一的。这一步我也自己做。Tidy 是给我自己留的工作,不交给 agent。
下午我做更复杂的活儿。但流程一样:小测试 → agent 实现 → 我读 diff → 提交 → 整理。
每天我至少 revert 三次。这是我和 agent 关系健康的标志——只要我还在 revert,就说明我还在判断。一旦我连续一周没 revert,我就要停下来检查:是真的它做得好,还是我已经放手太多了?
听上去无聊吗?是有点。但软件工程的稳态从来不是激动人心的,是无聊的、慢慢来的、可重复的。激动人心的东西总是在 incident 报告里。
十二、我们仍然没有银弹
Brooks 当年那篇《No Silver Bullet》,到现在已经四十年了。我每隔几年都重新读一遍。每次读完都更觉得他说得对。
他说,没有任何技术变革能在十年内让软件开发的本质难度减半。本质难度是:理解领域;表达精确;处理变化;跨人协调。这些事 AI 没在变。
AI 让”打字”变快了。这是好事。
AI 让”查文档”变快了。这是好事。
AI 让”试错”变便宜了。这是好事。
但理解一个新领域,没有 agent 能帮你跳过。
和一个不靠谱的 PM 沟通需求,没有 agent 能帮你跳过。
做一个会被未来五年用到的设计决定,没有 agent 能帮你跳过。
判断一个客户告诉你的小抱怨,会不会变成下个季度的大问题,没有 agent 能帮你跳过。
这些事不会因为 AI 而变。这些事是工程师存在的理由。
所以当有人问我”agent 会取代程序员吗”,我一般回答:”它会取代’敲代码这件事’里很大一块。它不会取代’判断’。”
如果你对这个回答失望,我也理解。这个时代希望听到激动人心的预言。我没有。我只有四十年的耐心和几句无聊的告诫。
十三、周一早晨,你可以做的一件事
如果你读到这里,我希望你周一早晨能做一件事。
只一件,不是十件。
挑你正在维护的代码里,最让你害怕动的那一段。
不是因为它写得最差——可能它根本不算差。是因为你心里那个”我不敢碰它”的小角落。
打开它。不要让 agent 做任何事。
你自己花一个小时,做一次 Tidy First。改名字、切方法、加注释、写一个最小的描述性测试——任何让”下一次有人读它时少一分恐惧”的小动作。
然后把它提交。提交信息写四个字:为下一次准备。
这件事和 AI 没关系。它和你,和你的团队,和你三年后的自己有关系。
我担心 AI 时代的工程师会忘记这件事——他们会把所有的小整理都让给 agent,最后发现没人愿意亲手碰那一段最害怕的代码。
只要你还愿意亲手碰,你就还在 driver’s seat。