一、重构入门:什么是重构、重构与重写的区别、重构的时机与原则
1.1 到底什么是重构?
先说说我自己的理解。重构,说白了就是在不改变软件外部行为的前提下,改善内部结构。
这句话听起来很官方,我换个说法:
- 功能不变——用户该点哪里还点哪里,结果一样
- 代码变好——可读性、可维护性、扩展性提升
举个例子。你写了一个函数,功能是计算订单总价:
// 重构前
function calc(a, b, c) {
let x = a * b;
if (c) x = x * 0.9;
return x;
}
这段代码能跑,但你看得懂吗?a、b、c 是什么?0.9 又是什么?
重构之后:
// 重构后
function calculateOrderTotal(price, quantity, hasDiscount) {
const subtotal = price * quantity;
const discountRate = hasDiscount ? 0.9 : 1.0;
return subtotal * discountRate;
}
功能完全一样。但变量名清晰了,逻辑拆开了,折扣率也显式定义了。这就是重构——不改功能,只改代码质量。
我的经验:重构不是炫技。我见过有人把一行代码拆成十行,美其名曰「重构」,结果反而更难懂了。好的重构,是让代码像读文章一样自然。
1.2 重构 vs 重写:别搞混了
这两个概念,很多新手容易混淆。我直接画个对比:
| 维度 | 重构 | 重写 |
|---|---|---|
| 外部行为 | 不变 | 可能变 |
| 代码量 | 逐步修改 | 推倒重来 |
| 风险 | 低(小步快跑) | 高(容易出大问题) |
| 耗时 | 持续进行 | 集中大段时间 |
| 适用场景 | 代码还能救 | 代码已经烂到根了 |
我举个例子你就明白了。假设你接手一个老项目:
- 重构:像给老房子翻新——换水管、刷墙面,但承重墙不动
- 重写:像拆了老房子重新盖——地基都要挖掉
我曾经在一个项目中,团队决定重写一个核心模块。结果呢?写了三个月,新系统上线第一天就崩了。为什么?因为老系统里那些「隐藏逻辑」——没人记得的边界条件、历史遗留的兼容处理——全被忽略了。
避坑指南:我曾经犯过这个错——觉得老代码太烂,不如重写。结果重写比重构多花了三倍时间,还丢了几个关键功能。记住:重构是手术,重写是投胎。能手术就别投胎。
1.3 什么时候该重构?
这个问题,我经常被问到。其实判断标准很简单,就三条:
- 代码有「坏味道」——比如函数太长、类职责不单一、重复代码太多
- 添加新功能很痛苦——改一处要动十处,测试跑半天
- 代码难以理解——你自己写的代码,两周后就看不懂了
我个人的习惯是:每次添加新功能前,先花 10-20 分钟重构相关代码。这叫「童子军规则」——让营地比你发现时更干净。
但也要注意,有些时候不适合重构:
- 项目马上要上线了——别动,稳住
- 代码没有测试覆盖——重构完你都不知道对不对
- 团队对业务理解不够——改错了没人发现
核心原则:重构的黄金时机是「你刚写完代码,但还没提交」的时候。这时候你对逻辑最清楚,改起来最快。
1.4 重构的三大原则
做了这么多年架构,我总结出三条铁律:
原则一:小步快跑
每次只改一点点。改完就跑测试,测试通过再改下一步。我见过有人一口气重构了十个文件,结果出了问题都不知道是哪一步引入的。
// 错误示范:一次改太多
// 1. 改了变量名
// 2. 拆分了函数
// 3. 改了算法
// 4. 加了缓存
// → 出问题根本不知道是哪步
// 正确做法:分四步走
// Step 1: 只改变量名 → 测试
// Step 2: 拆分函数 → 测试
// Step 3: 优化算法 → 测试
// Step 4: 添加缓存 → 测试
原则二:测试先行
没有测试的重构,就是裸奔。你想想看,改完代码你怎么知道对不对?靠肉眼检查?不靠谱。
我建议:重构前先写测试,把当前行为「固化」下来。然后每改一步,跑一次测试。测试全绿,说明没改坏。
原则三:不要同时重构和加功能
这是新手最容易犯的错。一边重构一边加新功能,出了问题你分不清是重构引入的,还是新功能有 bug。
我的习惯:先重构(不改功能),测试通过后,再加新功能。两个阶段严格分开。虽然看起来慢,但实际总时间反而更短——因为 debug 的时间大大减少了。
1.5 重构的常见误区
最后,我列几个常见的「坑」,你注意避开:
- 「重构就是优化性能」——错。重构优先提升可读性和可维护性,性能优化是另一回事
- 「重构需要专门立项」——错。好的重构是日常习惯,不是大工程
- 「重构是架构师的事」——错。每个开发者都应该会重构
- 「重构完代码就完美了」——错。代码永远有改进空间,重构是持续的过程
嗯,这一章就到这里。记住一句话:重构不是目的,而是手段。我们的目标不是写出「完美的代码」,而是写出「能持续演进的代码」。
下一章,我们聊聊具体的重构手法——如何识别代码中的「坏味道」,以及怎么动手改。