一、重构入门:什么是重构、重构与重写的区别、重构的时机与原则

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 什么时候该重构?

这个问题,我经常被问到。其实判断标准很简单,就三条:

  1. 代码有「坏味道」——比如函数太长、类职责不单一、重复代码太多
  2. 添加新功能很痛苦——改一处要动十处,测试跑半天
  3. 代码难以理解——你自己写的代码,两周后就看不懂了

我个人的习惯是:每次添加新功能前,先花 10-20 分钟重构相关代码。这叫「童子军规则」——让营地比你发现时更干净。

但也要注意,有些时候不适合重构:

  • 项目马上要上线了——别动,稳住
  • 代码没有测试覆盖——重构完你都不知道对不对
  • 团队对业务理解不够——改错了没人发现
核心原则:重构的黄金时机是「你刚写完代码,但还没提交」的时候。这时候你对逻辑最清楚,改起来最快。

1.4 重构的三大原则

做了这么多年架构,我总结出三条铁律:

原则一:小步快跑

每次只改一点点。改完就跑测试,测试通过再改下一步。我见过有人一口气重构了十个文件,结果出了问题都不知道是哪一步引入的。

// 错误示范:一次改太多
// 1. 改了变量名
// 2. 拆分了函数
// 3. 改了算法
// 4. 加了缓存
// → 出问题根本不知道是哪步

// 正确做法:分四步走
// Step 1: 只改变量名 → 测试
// Step 2: 拆分函数 → 测试
// Step 3: 优化算法 → 测试
// Step 4: 添加缓存 → 测试

原则二:测试先行

没有测试的重构,就是裸奔。你想想看,改完代码你怎么知道对不对?靠肉眼检查?不靠谱。

我建议:重构前先写测试,把当前行为「固化」下来。然后每改一步,跑一次测试。测试全绿,说明没改坏。

原则三:不要同时重构和加功能

这是新手最容易犯的错。一边重构一边加新功能,出了问题你分不清是重构引入的,还是新功能有 bug。

我的习惯:先重构(不改功能),测试通过后,再加新功能。两个阶段严格分开。虽然看起来慢,但实际总时间反而更短——因为 debug 的时间大大减少了。

1.5 重构的常见误区

最后,我列几个常见的「坑」,你注意避开:

  • 「重构就是优化性能」——错。重构优先提升可读性和可维护性,性能优化是另一回事
  • 「重构需要专门立项」——错。好的重构是日常习惯,不是大工程
  • 「重构是架构师的事」——错。每个开发者都应该会重构
  • 「重构完代码就完美了」——错。代码永远有改进空间,重构是持续的过程

嗯,这一章就到这里。记住一句话:重构不是目的,而是手段。我们的目标不是写出「完美的代码」,而是写出「能持续演进的代码」。

下一章,我们聊聊具体的重构手法——如何识别代码中的「坏味道」,以及怎么动手改。