重构模式
应用经证实的重构动作 — 提取、内联、移动、引入参数 — 保行为且可测。
为什么
重构是不改行为改结构,安全做到这点的唯一方式是在测试套件每步后可验证的小保行为步骤。大爆炸重写同时改行为和结构,测试失败时不知哪个改破坏。命名重构模式目录(Fowler 的)给你一组证实保行为的动作词汇:提取函数、内联变量、移动方法、引入参数对象。每个小到可单独提交、可被测试验证,组合它们通过一串安 全小步实现大结构变更。这是不重写而还设计债的方式。
何时使用
还已识别设计债时,或功能因结构对抗而难加时。绝不在修 bug 时(先修后重构)。
如何做
1. 开始前确保测试存在且绿 — 无测试 = 不重构,先写特征测试 2. 从 Fowler 目录选一个朝目标结构移动的模式 3. 以最小可能步应用;提交 "refactor: extract function X" 4. 跑测试;若红则该步非保行为 — 回退重思 5. 重复下一模式直到达目标结构 6. 每提交 <50 行 diff 使审查平凡且 bisect 有意义 7. 绝不把重构提交与功能/修 bug 提交混 — 混提交无法安全回退
示例
# 前:一个函数做 3 件事(难测、难扩展)
function processOrder(order) {
// 校验
if (!order.items || order.items.length === 0) throw new Error("empty");
// 计算
let total = 0;
for (const item of order.items) total += item.price * item.qty;
// 保存
db.orders.insert({ ...order, total });
return total;
}
# 步 1:提取 validateOrder(提交:"refactor: extract validateOrder")
function validateOrder(order) {
if (!order.items || order.items.length === 0) throw new Error("empty");
}
function processOrder(order) {
validateOrder(order); // ... 其余不变
}
# 测试绿?提交。
# 步 2:提取 calculateTotal(提交:"refactor: extract calculateTotal")
# 步 3:提取 saveOrder(提交:"refactor: extract saveOrder")
# 现在 processOrder 是 3 行组合 3 个命名可测函数。常见问题
- ⚠无测试重构 — 你在改结构和行为且分不清,这是重写非重构。
- ⚠一个提交大爆炸重构 — 测试坏时无法隔离 20 个动作哪个导致,只能全回退。
- ⚠一个 PR 混重构和功能 — 审查者分不清哪些是行为变更哪些是结构。
最佳实践
- ✓无绿测试绝不重构 — 若无测试先写钉住当前行为的特征测试。
- ✓一次提交一个重构动作带 "refactor:" 前缀 — 每提交保行为且可独立回退。
- ✓重构不进功能 PR — 纯重构 PR 绿则审查合并平凡;混的不然。