第 5 章:反馈环
出处
来源:tdd · diagnosing-bugs · prototype · research · 状态:已读
一句话
没有一条已经跑红过的命令,不准猜;没有预先约定的接缝,不准写测试;回答设计问题的代码从第一天就标明可丢。 三种环修三种盲:行为、缺陷、纸上谈不清的形状。
核心概念
测试先行是参考,不是司机
tdd 自己定位:它是红 → 绿循环的标准;跑这场会话的是你,或 /implement。
好测试经公共接口验证行为,不经实现细节。代码可以整段换;测试不该碎。读起来该像规格:「用户能用有效购物车结账」说的是能力,能活过重构,因为它不关心内部结构。
接缝是你观察行为、不必伸进内部的公共边界。测试住在接缝上,绝不对着内部。写任何测试之前,先写下要打的接缝,跟你确认。未确认的接缝上不准写测试。接口形状本身还在问(模块有多深、接缝该在哪、该暴露什么),去查 codebase-design 的词汇——那是参考,不是一场要跑的会话。
循环规则:
- 先红后绿。 先写失败测试,再写刚够通过的代码。不准预支后面的测试,不准加投机功能
- 一次一刀。 一个接缝、一个测试、一份最小实现
- 重构不属于这个循环。 它属于评审(
code-review),不属于红-绿。2026 年 6 月删掉重构阶段:agent 基本上从不做;评审和实现分成两场会话更好
三种反模式:
| 反模式 | 怎么认 |
|---|---|
| 和实现绑死 | mock 内部协作者、测私有方法、走旁路(直接查库而不是走接口)。一重构就碎、行为没变 |
| 同义反复 | 断言按代码自己的办法重算期望(expect(add(a,b)).toBe(a+b))。期望必须来自独立真源:已知字面量、做过的例子、规格 |
| 横切 | 先写完所有测试再写实现。批量测试验证的是想象中的行为,对真实变化不敏感,并且在理解实现之前就锁定了测试结构 |
公开缺口(issue #746):skill 决定接缝打在哪,不决定这次改动值不值得走循环。配置、接线、纯 CRUD 委托跑它,会从另一头走进同义反复。这个判断暂时归你或 CLAUDE.md。
诊断:环本身才是 skill
diagnosing-bugs 把「先读代码再猜」整段堵住。六阶段,只有明确理由才能跳。
第 1 阶段就是这条 skill。 其余是机械的。有一条会在这个缺陷上变红的紧反馈,原因就找得到;二分、假说、埋点都只是在消费它。没有它,盯代码救不了你。
完成标准:你能说出一条命令,并且已经跑过至少一次(出示调用和输出,密钥打码),同时:
- 会变红:走的是真实缺陷路径,断言的是用户的那个症状,不是「没报错」
- 可重复:每次同一判决(闪烁型:把复现率钉到能调试)
- 快:秒级,不是分钟
- agent 能自己跑:人在环里只经附带的 HITL 脚本
抓住自己在这条命令存在之前读代码编理论,停。没有会变红的命令,就没有第 2 阶段。
造环的梯子(大致按偏好):失败测试 → curl / HTTP 脚本 → 带夹具的 CLI → 无头浏览器 → 回放抓到的请求 → 一次性架子 → 属性 / 模糊测试 → 可交给 git bisect run 的二分架子 → 新旧版本差分 → 最后才是人在环里的 bash。
紧才是目标。30 秒还闪烁的环,几乎等于没有;2 秒且可重复的,才是调试超能力。闪烁型缺陷追求的不是干净复现,是更高复现率:触发 100 次、并行、加压,直到 50% 那种能调;1% 不能。
第 2 阶段:复现并最小化。每切掉一点就重跑,留下的每一项都得是承重的——去掉任何一个,环就变绿。第 3 阶段:先排 3–5 个可证伪的假说再测任何一个。「如果 X 是原因,改 Y 缺陷会消失 / 改 Z 会更糟。」说不出预测的是感觉,丢掉或磨尖。列表先给你看;你不在就按它的排序继续。第 4 阶段:一次改一个变量;调试器优先于日志;日志带 [DEBUG-a4f2] 这种前缀,清场时一次 grep。性能回归:先量基线再二分,日志通常是错工具。第 5 阶段:在正确接缝上,先写回归测试再修。接缝太浅(缺陷需要多个调用者,单调用者测试给假信心)——找不到正确接缝,本身就是发现,交给下一章的架构调查。第 6 阶段:原环不再复现、回归测试过(或接缝缺失已记录)、所有调试埋点删掉、一次性原型删掉或标清、正确的那个假写进提交说明。
密钥一律打成 <REDACTED>。环对着环境变量建,凭证留在环境里,不出现在你展示的输出里。
原型:丢弃是写法约束,不是销毁承诺
原型是回答一个问题的可丢代码。问题决定形状,两条枝差很远,选错就整段浪费:
- 「这套逻辑 / 状态模型感觉对吗?」→ 一份可分享的 HTML,自由按钮 + 分步走查,非开发也能开
- 「该长什么样?」→ 同一路由上好几套差别很大的界面,URL 参数和底部条切换
两条共用的规则:从第一天就标明可丢,放在靠近将来真会用它的地方;一命令能跑;默认不持久化(持久化若是问题本身,用带「PROTOTYPE, wipe me」名字的草稿库);不打磨——没测试、没超出能跑的错误处理、没抽象;每次动作把相关状态打满。做完:有效的决定折进真代码;原型本身作为一手材料提交到主线以外的 prototype/<name> 分支,实现议题上留指针。主线只留被验证过的决定。
主链里它是追问和规格之间的弯路,经 /handoff 进出,因为原型住在自己的目录里。纸上谈不清时随时可以打。
调研是后台的一手材料
research 派后台 agent 去查,你继续干活。对着一手材料(官方文档、源码、规格、第一方 API),不是对着二手综述。发现写成一份带引用的 Markdown,放到仓库已经放这类笔记的地方。探路图上的调研票走这条 skill;主链上它不是一步,是「决定还缺一个仓库外的事实」时的支线。
它不在对话里答你。产出是一份能交给下一场会话、也能丢掉的文件,不是窗口一清就消失的口头结论。调研和追问的分界是带回来的东西能活多久:这周这个库的鉴权怎么工作,是调研;你要留下的决定,是追问。
已知毛边(issue #530):skill 让调用方派一个后台 agent,却不限制 agent 类型,于是被派出去的那个还握着同一套指令,再派一层。有人量过,一次调研叠成三趟并行,大约 45 万 token,重复的那趟半小时后才在视线外结束。Claude Code 和 Codex 都复现过,还没有随发行的修复。打完之后看后台任务列表,重复的停掉。反面失败也有:全局指令禁止再委派时,后台 agent 会礼貌拒绝,skill 静默什么都不做。
关键洞察
- 红是二值可观察状态,不是心情。
writing-for-agents把「你相信的循环」收成引导词red:环在这个缺陷上变红,或者没有。诊断第 1 阶段把这个词当成完成标准。 - 三种环不要混。 测试先行造行为;诊断追已知症状;原型回答设计问题。拿诊断去审计「仓库瓶颈在哪」,或拿原型去追缺陷,都是用错环。
- 接缝把第 4、5、6 章焊在一起。 规格约定它,测试先行只在那里写,诊断在那里锁回归,深模块用「接口就是测试面」衡量深度。接缝找错,四章一起偏。
个人思考
对照 Book 1 06-错误与恢复:那里说错误是主路径,恢复要预先设计。Matt 把同一判断用在 agent 会话里——没有红环的诊断,就是在「正常情况下」里猜。下一章处理红环打在错误形状上的那种失败:模块太浅,测试只好伸进内部。