Tidy First,再让 Agent 进门
副标题:Kent Beck 风格——小步、短环、第一人称
一、上周一早上
上周一早上,我坐在厨房里,喝着昨晚剩的咖啡,看着 agent 在我的屏幕上自己跑。它改了七个文件。我没看完。
我写软件已经四十年了。但那一刻心里咯噔了一下:屏幕上的 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 在笼子里跑。”
我同意要写测试。但我想做一个区分。
测试是脚手架。它支撑你正在建的那堵墙,让你下一步敢动。它在你不确定时给你“绿灯还在”的安心感。它不是来“控制 agent”的,它是来支持所有改代码的人——我、你、agent——让每个人敢做下一步。
把测试当笼子,你就会写出大量坏测试:测实现细节的、测每个 getter/setter 的、测那些不会变也不重要的东西。覆盖率看着高,但对“敢不敢动”没帮助,对“动错了能不能发现”也没帮助。
好测试不告诉你代码长什么样,而是告诉你代码做什么。它不在正常修改时碍事,只在你做错事时变红。
测试不是越多越好,是越能给你勇气越好。
agent 时代还有一个新陷阱:让 agent 自己写测试。它会写出大量看似合理、实则只是“读了一遍代码再用断言抄一遍”的测试。这些测试永远不会变红,因为它们没有独立判断——只是一面镜子。你以为被保护着,其实只是被镜子盯着。
我现在让 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 行为里让我心里咯噔一下的信号。
“我看不懂的胜利”。agent 说它把测试跑通了。我看一眼 diff,说不出哪里不对,也说不出哪里对。这种“看不懂”最危险,通常意味着 agent 走了一条聪明但偏的路。我的规矩:看不懂的胜利不算胜利。要么读懂,要么 revert。
“修一处,动十处”。一个本应局部的修复,agent 改了一串看似无关的文件。每一处它都有理由,但放一起就是泄漏的边界。这通常说明系统的某个抽象放错了位置。遇到这种情况我会停下来,先处理结构问题。
“它越来越自信”。agent 跑了几轮都过,然后开始一次改更多文件、提交更长信息、说话更笃定。这是在“陷得深了”。每次看到这种势头,我就让它停下来,讲一遍做了什么、为什么。讲得清就继续,讲不清就回到上一个绿灯。
这三种 smell 没什么神秘的,就是放大版的 code smell。人写代码会犯的错,agent 一个早上能犯一百次。
九、关于 courage
我在 XP 那本书里把 courage 列为四个核心价值之一:communication、simplicity、feedback、courage。
很多人不理解 courage 为什么是工程价值,觉得那是性格。不是。courage 是结构性的。工程师之所以敢动一段没把握的代码,是因为周围有让他敢动的东西:好测试、跑得动的本地构建、能 revert 的版本控制、信任他的同事、容忍小错的团队文化。凑齐了,courage 就长出来;缺了,再勇敢的人也会变保守。
agent 时代,这个词值得重新讲一次。
agent 让人更有 courage——你敢试更激进的重构、更大胆的实验、更多的 spike,因为成本低了。但 agent 也会侵蚀 courage——当它一次产出几千行你看不全的代码,你会越来越不敢动。那些代码变成了“陌生区域”。你甚至开始拒绝重构,心想:“让 agent 自己 review 自己吧,反正它写的我也读不懂。”
这是退化。个体的 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 让打字变快了,让查文档变快了,让试错变便宜了——这些都是好事。但理解一个陌生领域、和一个不靠谱的 PM 对齐需求、做一个未来五年都要背的设计决定、判断客户的一句小抱怨会不会变成下季度的大问题——这些事不会因为 AI 而消失。这些才是工程师存在的理由。
所以当有人问我“agent 会不会取代程序员”,我一般回答:“它会取代‘敲代码’里很大一块,但不会取代‘判断’。”
如果你对这个回答失望,我理解。这个时代想听激动人心的预言。我没有。我只有四十年的耐心和几句无聊的告诫。
十三、周一早晨,你可以做的一件事
如果你读到这里,我希望你周一早晨能做一件事。
只一件,不是十件。
挑你正在维护的代码里,最让你害怕动的那一段。
不一定是写得最差的——可能它根本不算差。只是你心里那个“我不敢碰它”的小角落。
打开它。不要让 agent 做任何事。
自己花一个小时,做一次 Tidy First。改名字、切方法、加注释、写一个最小的描述性测试——任何让“下一次有人读它时少一分恐惧”的小动作。
然后提交。提交信息写四个字:为下一次准备。
这件事和 AI 无关。它和你、你的团队、你三年后的自己有关。
我怕 AI 时代的工程师会忘掉这件事——把所有小整理都让给 agent,直到没人愿意亲手碰那段最害怕的代码。
只要你还愿意亲手碰,你就还在 driver’s seat。