项目重构入门:重构的定义、重构与重写的区别、重构的时机与原则、重构的代码坏味道

大家好,欢迎来到项目重构入门这一讲。

说实话,我见过太多团队在「改代码」这件事上栽跟头。有的人一看到代码乱,就嚷嚷着要重写;有的人则死守着烂代码不敢动,生怕一改就崩。这两种极端,我都经历过。

今天我们就来聊聊重构这件事。什么是重构?什么时候该重构?什么时候该重写?以及,哪些代码在告诉你「我该被重构了」。

一、重构的定义:不是你想的那样

先给个准确定义。重构,英文叫 Refactoring,指的是:在不改变软件外部行为的前提下,改善内部结构。

注意两个关键词:

  • 不改变外部行为——用户看到的、系统对外提供的功能,一点都不能变。
  • 改善内部结构——代码的可读性、可维护性、扩展性变好了。

说白了,重构就是「给代码做内部装修」。墙刷白了,水管换新了,但房子的户型、面积、门牌号,统统不变。

核心要点:重构不是「推倒重来」,而是「小步快跑」地优化。每次改动后,都要确保测试通过。

我个人习惯把重构比作「整理房间」。你不需要把房子拆了重建,只需要把东西归归类、擦擦灰、扔掉没用的杂物。房间还是那个房间,但住起来舒服多了。

二、重构 vs 重写:别搞混了

这是新手最容易混淆的两个概念。我直接画个对比表:

维度 重构 重写
定义 改善内部结构,不改变外部行为 从头开始写一套新的
风险 低,逐步进行 高,一次性替换
时间成本 持续投入,积少成多 集中投入,周期长
对业务的影响 几乎无影响 可能中断业务
适用场景 代码结构差但逻辑正确 技术栈过时、架构彻底崩塌

我在项目中遇到过最典型的例子:一个老系统用了十年的 Struts 1,代码耦合得像一团麻花。团队里有人提议「全部用 Spring Boot 重写」。我当时就反对——你想想看,业务逻辑几百个接口,重写得测到猴年马月?

后来我们怎么做?先重构核心模块,把业务逻辑从 Struts Action 里抽出来,封装成独立的 Service。等核心模块稳定了,再逐步替换掉 Struts 的视图层。整个过程花了半年,但业务一天都没停过。

避坑指南:我曾经见过一个团队,花三个月重写了一个模块,结果上线第一天就崩了。为什么?因为重写过程中,业务逻辑被「简化」了,漏掉了十几个边界情况。记住:重写不是「重新发明轮子」,而是「重新造一个可能更差的轮子」。

三、重构的时机:什么时候动手?

这个问题,我经常被问到。我的回答是:不要等「有空了」再重构,而是「顺手」就做。

具体来说,有这几个信号:

  • 添加新功能时——如果现有代码让你很难加新功能,那就是重构的信号。先重构,再加功能,反而更快。
  • 修复 Bug 时——如果同一个 Bug 反复出现,说明代码结构有问题。顺手重构一下,一劳永逸。
  • 代码审查时——看到「坏味道」代码,当场提出来,当场改。别等以后。
  • 技术债务积累到一定程度——比如每次改代码都要花半天理解逻辑,那就该动手了。

嗯,这里要注意:重构不是「大扫除」,而是「日常保洁」。每天花 15 分钟做一点,比攒一年搞一次大扫除要有效得多。

四、重构的原则:别乱来

重构不是随心所欲地改代码。我总结了几条铁律:

  1. 先有测试,再重构——没有测试保护,重构就是裸奔。哪怕只是改一个变量名,都可能引发连锁反应。
  2. 小步提交,频繁验证——每次只改一个点,改完就跑测试。不要一次改 100 行代码然后祈祷能跑通。
  3. 不要同时重构和加功能——这两件事要分开做。先重构,再加功能;或者先加功能,再重构。混在一起,出了 Bug 你都不知道是哪个环节的问题。
  4. 保持代码可运行——重构过程中,任何时候代码都应该能编译通过、能运行。如果做不到,说明步子迈大了。

我的小技巧:每次重构前,先跑一遍所有测试,确保「绿条」。重构过程中,每改 5 分钟就跑一次测试。一旦出现红条,立刻回退到上一个绿条状态。这样永远不会陷入「改崩了但不知道从哪开始修」的窘境。

五、代码坏味道:识别「该重构了」的信号

「坏味道」这个词是 Martin Fowler 提出来的。它不是 Bug,但暗示着代码可能有问题。我挑几个最常见的说说:

1. 重复代码

同一个逻辑,出现在三个不同的地方。改一个地方,另外两个忘了改,Bug 就来了。

// 坏味道:重复代码
function calcPriceA(items) {
  let total = 0;
  for (let item of items) {
    total += item.price * item.quantity;
  }
  return total * 0.9;
}

function calcPriceB(items) {
  let total = 0;
  for (let item of items) {
    total += item.price * item.quantity;
  }
  return total * 0.8;
}

// 重构后:提取公共方法
function calcTotal(items) {
  let total = 0;
  for (let item of items) {
    total += item.price * item.quantity;
  }
  return total;
}

2. 过长函数

一个函数几百行,滚动条都要拉半天。这种函数往往做了太多事,违反了「单一职责原则」。

3. 过大的类

一个类里塞了几十个方法,字段几十个。这种类通常需要拆分成多个小类。

4. 散弹式修改

改一个需求,需要修改十几个文件。这说明代码的「内聚性」太差,相关逻辑散落在各处。

5. 数据泥团

三个字段总是同时出现,比如 name、age、gender。那就应该把它们封装成一个类。

6. 基本类型偏执

用 String 表示电话号码、用 int 表示金额。应该用专门的类型来封装,比如 PhoneNumber、Money。

一句话总结:坏味道就是「让你觉得不舒服」的代码。相信你的直觉。如果你觉得某段代码「怪怪的」,那它大概率真的有问题。

六、知识体系总览

下面这张图,是我自己梳理的「重构入门知识体系」。你可以把它当作一张地图,随时回来看看:

项目重构入门 重构的定义 不改变外部行为 改善内部结构 重构 vs 重写 重构:小步优化 重写:推倒重来 重构的时机 加功能时 修Bug时 代码审查时 重构的原则 先有测试 小步提交 功能与重构分离 代码坏味道 重复代码、过长函数 过大的类、散弹式修改 数据泥团、基本类型偏执 核心思想:小步快跑,持续改善 不要等「完美」再动手,从今天开始

好了,这一讲的内容就到这里。记住:重构不是一次性的「大工程」,而是融入日常开发的「好习惯」。从今天开始,看到坏味道就顺手改一改,你的代码会感谢你的。