本节摘要:绿灯阶段的核心纪律是"最小"——只写让当前测试通过的代码;重构阶段的核心纪律是"只改结构不改行为",且每轮都做。本节给出"最小"的三条判据、常用重构手法清单与节拍控制法,并用密码校验器演示一次完整的整理。读完你应能让循环的第段(绿、重构)稳定运转,不再积压"有空再整理"的债务。
某天下午,灯塔小组的工位上停着两行红字:密码校验器的首条测试还红着。新人小林挽起袖子就想一口气把长度、数字、大写、特殊字符全写了——反正都是要做的。老周按住他:"这条测试只审长度。你今天多写的每一行,都是没有测试作证的自白。"这是绿灯阶段每天都要打的仗:技术上有能力写更多,纪律上只许写刚好够的那部分。
"最小实现"不是鼓励你写丑代码,而是划定举证责任:任何超出当前测试要求的功能,都没有证据表明它被需要。落到操作上,三条判据帮你在写与不写之间做裁决:
回到密码校验器,最小实现长这样:
# shop/password.py from dataclasses import dataclass, field @dataclass class Rating: valid: bool reasons: list[str] = field(default_factory=list) def rate(password: str) -> Rating: reasons = [] if len(password) < 8: reasons.append("长度不足") return Rating(valid=not reasons, reasons=reasons)
小林想顺手加上"缺数字"的判断,被 not reasons 这个表达式拦住了:它还没有对应的测试,加了也不会有红灯为它作证。留到下一轮循环,让它自己的红灯把它带进来——这就是"实现是被测试逼出来的"的字面意思。
⚠️ 最小不是偷工:有人把最小理解成
return Rating(valid=False, reasons=["长度不足"])这种写死答案的伪实现。伪实现合法,甚至是个流派(先走通流程再泛化),但下一轮循环必须把它换掉,不能让它蒙混过关——判据就是新红灯来时它必死。
测试全绿,代码行为正确,现在可以放心动结构。TDD 把重构钉进节律而不是挂在口号上,靠的就是这个时机:每轮绿灯后,花三到五分钟整理。整理什么?按需从下面这份手法清单里挑,一次别超过三种:
| 手法 | 动作 | 典型信号 |
|---|---|---|
| 提取函数 | 把一段意图完整的代码拎成具名函数 | 注释比代码长、函数超过屏 |
| 重命名 | 变量、函数、参数改成说人话的名字 | 名字需要注释解释 |
| 防卫子句 | 提前返回,拍平嵌套 | if 里套 if,心算嵌套深度 |
| 消除重复 | 相似代码合并成一个实现 | 改处要同步改多处 |
| 魔法值具名 | 魔法数字字符串提成常量 | 代码里躺着 8、0.85 这类裸值 |
| 参数对象化 | 一组总同进退的参数打包 | 函数签名越来越长 |
| 移动函数 | 函数搬到更亲近它的类或模块 | 函数老问别人的数据 |
| 合并条件碎片 | 同一对象的系列判断收拢 | 一堆 if 各查各的标志位 |
| 以查询代临时变量 | 复杂中间结果提成具名查询 | 临时变量需要注释解释 |
表是菜单不是任务——每次重构窗口只挑一两种,挑的标准是"哪个信号此刻真实存在"。手法清单的完整版在任何一本重构词典里都有,此处收录的是红绿节律里高频出场的那批:它们共同的特点是几分钟内可完成、且天然把代码朝"下条测试好写"的方向推。
对刚才的实现动手:密码最短长度硬编码在条件里,且校验规则注定要长一串,先把"单条规则"抽象出来,给后续规则留好插槽:
# 重构前:规则内联在 rate 里,加规则就得改函数体 def rate(password: str) -> Rating: reasons = [] if len(password) < 8: reasons.append("长度不足") return Rating(valid=not reasons, reasons=reasons) # 重构后:规则收敛为函数清单,rate 只负责汇总 MIN_LENGTH = 8 def _check_length(password: str) -> list[str]: return [] if len(password) >= MIN_LENGTH else ["长度不足"] def rate(password: str) -> Rating: reasons = _check_length(password) return Rating(valid=not reasons, reasons=reasons)
每改完一步跑一遍测试,绿了再动下一处。行为没有分毫变化——测试可以为证——但下一个规则到来时,只需要往函数清单里加一项,不用再拆开 rate 的内脏。重构的红利总是兑现在下一轮,这就是它值得每轮都做的原因。

老周团队的规矩是:一轮循环十分钟为软上限。红灯阶段两三分钟,绿灯阶段两三分钟,重构阶段三五分钟,超时即视为切片过厚或实现思路跑偏。计时不是形式主义——循环速度就是反馈速度,反馈速度决定你能承受多大的重构、多小的提交粒度。如果发现自己连续几轮都超时,回头检查红灯的切片是不是混进了多个行为。
💡 提交也是节律的组成部分:每轮循环结束就 commit,提交信息用测试名。一天下来提交历史就是当天的需求账本,回滚粒度天然细到行为级。