有一类重构,团队一直在做,立刻从中受益,却几乎从不给它命名。
它是你碰那个吓人的文件之前要做的活。功能需求把你逼进那个混乱模块。事故来了,bug 藏在一个看起来自带天气系统的方法里。
你不是在重新设计系统。你不是在引入新的抽象。你也不是用某种聪明方式“改进”任何东西。
你只是把代码弄到足够可读,让你能开始工作。
我开始把这叫作 Type 0 重构。
Type 0 重构是一种预备性的、保持行为不变的清理,它让代码在你做架构重构、性能工作或功能工作之前,先变得更容易推理。
它就是“重装厨房之前,先把地板擦干”的那一步。大多数团队已经在非正式地做它。给它一个名字,它就变成了一个共享工具。
Type 0 存在的真正原因:人的工作记忆有预算
这个想法背后的直白真相是:
我的大脑(你的也是)并不是为了在时间压力下可靠地调试一个 2000 行方法而设计的。
这不是个人缺陷。认知就是这样工作。
调试要求你同时把这些东西放在脑子里:
- 当前执行路径
- 相关状态
- 每个变量实际意味着什么
- 可能分支的集合
- “如果发生这个,那么……”的后果
在小代码里,这还可控。
在大代码里,再加上高圈复杂度,它就变成了概率性猜测。你仍然可能走运,但成本高,风险也高,尤其是在 hotfix 期间。
Type 0 是一个实际回应:它让你快速购买清晰度,而不承担“真正重构”的成本和风险。
为什么叫 “Type 0”
这个名字不是从什么宏大理论里来的。它来自一个高压时刻。
我当时在做一个 hotfix。bug 被埋在一个方法里,而那个方法实际上就是自己的小宇宙——大约 2000 行。
bug 在概念上并不难。难的是这个方法。
每一个“如果……会怎样”都会分裂成十个更多的问题,而那些分支并不是有用的那种。那是偶然复杂度:噪音、重复、不清楚的命名,以及和调试所需心智模型不匹配的结构。
我需要的不是完美。我需要的是可调试性:
- 每屏更少的分支
- 更清楚、有名字的“步骤”
- 更少的噪音
- 更少时间重新解析刚刚读过的东西
但时间压力不允许更大的重构,也不允许一次“惯用写法的重新设计”。负责任地做那件事要半天(甚至更多),还包括手动测试。在 hotfix 窗口里,那不是纪律;那是赌博。
于是我让一个 LLM 为这个类和这个方法建议重构机会——没告诉它原因。
它回来给了我一份四种“类型”重构的列表。都合理。都适用。也都对那个时刻来说太贵。
然后它礼貌地问:
“Should I start with Type 1?”
那时我回复:
“No. Let’s start with Type 0.”
我当场定义了 Type 0:一组受约束、机械性的修改,用来降低复杂度、提高可读性,但不改变行为或架构。
那个方法变得可以导航了。我的大脑又能跟上执行路径了。我找到了 bug,修掉了它,并且没有带着附带伤害发布。
这就是我喜欢 Type 0 这个名字的原因:它是你在“真正重构”类型之前做的重构——尤其是在你有压力,需要一种安全方式快速创造清晰度的时候。
Type 0 解决的问题
大多数重构建议都假设你已经能_看见_设计。
在真实代码库里:
- 方法很长,而且承担多种目的
- 重复表达式和偶然复杂度藏住了意图
- 变量很隐晦(
$e、$tmp、$res) - 死代码和未使用 import 制造心智噪音
- 代码的“形状”乱到连小改动都让人觉得危险
当你在这上面尝试“真正重构”(边界、模式、移动职责)时,你是在不确定性上堆更多不确定性:
- 你不容易判断自己正在保留什么行为
- 你无法预测爆炸半径
- review 会退化成主观争论
- 人们开始害怕碰这些东西,而混乱继续复利
Type 0 是你先降低认知负荷的方法。 它创造一个稳定基础,让更深的工作可以安全发生。
什么时候拿起 Type 0
Type 0 在这些情况下最有价值:
- 你必须快速调试(hotfix、事故),而代码太大 / 分支太多,无法安全推理
- 你觉得自己“迷失在方法里”,反复重读同一段,因为结构帮不上你的工作记忆
- 代码是正确的,但不可读;你承担不起“清理逻辑”,只能把逻辑暴露出来
- 你想在更深工作之前降低风险(你知道之后会重构,但首先需要当前行为的清晰地图)
- 你想把部落知识变成可读结构,让调试不再依赖某一个人
Type 0 不是奢侈品。在这些场景里,它常常是最快重新拿回控制权的方法。
一个可以给团队使用的定义
Type 0 重构是一组微重构,用来在不改变行为或架构的前提下提升可读性和可维护性。
它刻意受约束。约束就是这个特性本身。
Type 0 包含四个强制子模式:
- 0a. 方法提取
- 0b. 简洁性
- 0c. 同理心(纯可读性)
- 0d. 删除死代码
并遵守三条硬规则:
- 不改变行为
- 不改变架构
- 除了这四种模式之外,不做“聪明”的改进
如果你违反这些规则,你做的就不再是 Type 0——你已经进入了另一类工作,而那需要不同的协调、不同的 review 严谨度,通常也需要不同的测试策略。
为什么非要命名?
因为命名会改变团队协作的方式。
- “这个 PR 里我只做 Type 0”会告诉 reviewer 该看什么:行为保持和可读性,而不是架构争论。
- “我们需要先对这里做 Type 0,再重构”是在诚实承认代码还没准备好承接更深的改变。
- “我们把 Type 0 当作 Step 0 来做”创造了一个小仪式,防止你在混乱之上继续搭东西。
四个子模式
0a. 方法提取(基础)
目标: 把大方法拆成小而聚焦的方法,让人可以线性读懂意图。
经验规则:
- 拆解那些长到无法放进工作记忆的方法
- 每个提取出来的方法应该只做一件事,并且有描述性的名字
- 提取有意义的步骤,而不是任意 N 行代码块
为什么它有效(尤其对调试):
- 小方法为执行路径创造标签
- 2000 行滚动变成一个短的编排方法,你可以在脑子里逐步走过
- 你可以在语义边界上打断点(“验证输入”、“构建查询”、“应用过滤器”),而不是到处狩猎
0b. 简洁性(减少偶然复杂度)
目标: 移除视觉噪音,让意图浮出来。
例子:
- 把重复表达式提取成局部变量
- 把重复的日志上下文 / key 字符串 / URL 片段提取成变量
- 优先使用能直接表达意图的语言特性
- 简化过度冗长的插值
为什么它有效:
- 它降低认知负荷
- 它让 diff 更小,改动更安全
- 它防止复制 / 粘贴漂移
0c. 同理心(纯可读性)
目标: 写给下一个人看,而不是写给编译器看。
同理心意味着:
- 使用描述性变量名(避免
$e、$d、$tmp,除非真的显而易见) - 在一个模块内保持术语一致
- 重命名具有误导性的名字
- 让代码自我说明
试金石:
如果有人在凌晨两点的事故中读这段代码,它会帮助他们把执行路径放在脑子里吗?
0d. 删除死代码(移除谎言)
目标: 删除所有假装重要、其实不重要的东西。
例子:
- 未使用的 private 方法
- 未使用的 imports
- 注释掉的旧方案
- 已废弃且没人调用的 helpers
为什么它有效:
- 代码越少,误解的东西就越少
- 搜索结果会变得可信
Type 0 不是什么
Type 0 不是:
- 改变服务边界
- 引入新的抽象或模式
- 重新架构一个 workflow
- 替换库
- 在层之间重新分配职责
- “修复”你怀疑有错的逻辑(除非你明确声明这是行为改变,并测试它)
如果你发现自己在说:
- “既然我在这里,不如也……”
- “如果我们这样会更好……”
- “我们大概应该重新设计……”
你可能正在离开 Type 0。这本身不坏——但应该是有意为之。
核心承诺:保持行为不变(以及如何让它是真的)
Type 0 只有在团队信任这个承诺时才有效。
而且是的,你怀疑得对:方法提取可能意外改变行为(early returns、变量作用域、求值顺序、异常行为)。
所以 Type 0 需要纪律来保持诚实:
照原样提取,然后再重命名 / 清理。
- 第一遍:把代码移进方法,不改变逻辑
- 第二遍:应用简洁性 + 同理心
- 第三遍:删除死代码
实用护栏:
- 不要为了“可读性”重新排列条件检查
- 不要用“等价”逻辑替换逻辑,除非你已经在 Type 0 之外
- 小心那些原本在共享作用域里的变量
- 把“小的”控制流差异当作真正差异看待
如果你有任何安全网,哪怕很薄:
- 跑一个聚焦测试
- 重放失败场景
- 验证你正在触碰的那一条路径
Type 0 讲的是快——但这种快来自降低认知复杂度,不是来自跳过安全。
把 Type 0 变成可重复的团队仪式
1) 决定范围(timebox 有帮助)
例子:
- “调试前先对 hot path 做 Type 0。”
- “只对这个 bug fix 会碰到的路径做 Type 0。”
2) 找到代码的“脊柱”
找到入口方法和分支点。通过提取,把这条脊柱变成一个可读叙事。
3) 按顺序应用四个子模式
方法提取 → 简洁性 → 同理心 → 删除死代码。
4) 在 PR 里保留一个 “Type 0 checklist”
- 不改变行为(输入 / 输出不变)
- 不做架构移动
- 方法已提取,并命名为有意义的步骤
- 重复表达式已在能提升清晰度的地方提取
- 变量已重命名;术语一致
- 死代码和未使用 imports 已删除
结尾想法
Type 0 重构是开发者可以作出的最简单承诺:
“我会让这段代码比我发现它时更容易工作——同时不改变它做的事。”
有时候它是“有就更好”。
有时候,它是人在高复杂度混乱里安全快速移动的唯一办法——尤其是在 hotfix 期间。

评论