1、重构入门:什么是代码重构、重构与重写的区别、重构的时机与原则、.NET项目中的常见坏味道

1.1 到底什么是代码重构?

先说说我自己的理解吧。重构这个词,很多人一听就觉得高大上,其实说白了就是——在不改变代码外部行为的前提下,调整内部结构

你想想看,我们平时写代码,是不是经常遇到这种情况:一个方法越写越长,一个类越堆越臃肿,改一个地方要牵连七八个文件。这时候,你需要的不是重写,而是重构。

我记得刚入行那会儿,带我的老大哥跟我说过一句话,我一直记到现在:「重构不是把代码推倒重来,而是像整理房间一样,把东西归位」。嗯,这个比喻很贴切。

核心定义:重构 = 调整内部结构 + 保持外部行为不变。说白了,用户看到的还是那个功能,但代码内部已经焕然一新。

1.2 重构 vs 重写:别搞混了

这个问题我经常被问到。很多同学一上来就问:「老师,我这段代码太烂了,要不要重写?」我的回答通常是:先别急,看看能不能重构

这两者的区别,我用一个表格给你讲清楚:

维度 重构 重写
风险 低,逐步改进 高,容易引入新bug
时间成本 可控,小步快跑 不可控,可能延期
业务影响 几乎无影响 需要重新测试
适用场景 代码逻辑尚可,结构混乱 代码彻底腐烂,无法维护

我在项目中遇到过最典型的例子:一个老系统,业务逻辑全写在一个 3000 行的 Service 类里。当时团队有人提议重写,我拦住了。为什么?因为业务规则太复杂,重写一遍,光梳理需求就要三个月。后来我们花了两个星期,把大方法拆成小方法,把重复逻辑提取成公共方法,把数据访问层抽离出来。嗯,效果还不错,系统又能跑了。

避坑指南:我曾经见过一个团队,因为嫌弃代码烂,直接重写整个模块。结果写了半年,业务需求变了三回,最后新代码还没上线,旧代码已经没人维护了。记住:重写是最后的选择,不是第一选择

1.3 什么时候该重构?什么时候不该?

这个问题其实没有标准答案,但我个人习惯遵循「三次原则」:

  • 第一次遇到烂代码:先忍着,把功能做完再说
  • 第二次遇到同样的烂代码:开始思考,是不是设计有问题
  • 第三次遇到:别犹豫了,该重构了

为什么会这样?因为很多时候,你第一次看到一段代码觉得烂,可能是你还没理解业务。等第三次再碰到,说明这个坏味道已经影响到你的开发效率了。

另外,还有几个明确的信号:

  • 改一个bug,引入三个新bug —— 代码耦合度太高了
  • 加一个功能,要改五个文件 —— 职责分配不合理
  • 代码审查时,没人能看懂 —— 可读性太差

我的小技巧:每次提交代码前,花 5 分钟看看自己刚写的代码。如果觉得「这段代码下周我自己都看不懂」,那就立刻重构。别等到以后,以后只会更忙。

1.4 重构的原则:别乱来

重构不是瞎改,得有章法。我总结了三条铁律:

  1. 小步提交,频繁测试 —— 每次只改一个点,改完就跑测试。别想着一次改完所有问题,那是重写。
  2. 保持行为不变 —— 重构前后,单元测试应该全部通过。如果测试挂了,说明你改的不只是结构。
  3. 先有测试,再重构 —— 没有测试保护的重构,就像走钢丝。我吃过这个亏,真的。

我曾经接手过一个项目,没有单元测试,代码耦合得像一团麻花。我花了整整一周,先给核心逻辑补了测试,然后才开始重构。虽然慢,但每一步都走得踏实。

1.5 .NET项目中的常见坏味道

说了这么多理论,咱们来点实际的。在 .NET 项目里,我见过最多的坏味道有这些:

1.5.1 长方法(Long Method)

一个方法几百行,里面既有业务逻辑,又有数据访问,还有日志记录。你想想看,这样的方法谁能看懂?

// 坏味道示例
public void ProcessOrder(int orderId)
{
    // 100行验证逻辑
    // 200行业务处理
    // 50行数据库操作
    // 30行发送邮件
}

重构思路:每个方法只做一件事。验证、业务、数据、通知,各拆一个方法。

1.5.2 过大的类(God Class)

一个类承担了太多职责。比如一个 OrderService,既管订单创建,又管库存扣减,还管支付回调。说白了,这就是「上帝类」。

重构思路:用单一职责原则拆分类。一个类只负责一个业务领域。

1.5.3 重复代码(Duplicated Code)

同一个逻辑,在三个地方各写了一遍。改一个地方,另外两个忘了改,bug 就来了。

重构思路:提取公共方法,或者用继承/接口来消除重复。

1.5.4 过度耦合(Tight Coupling)

类与类之间直接 new 出来,没有接口抽象。改一个实现,所有调用方都要改。

// 坏味道示例
public class OrderService
{
    private EmailSender _sender = new EmailSender(); // 直接依赖具体类
}

重构思路:面向接口编程,用依赖注入解耦。

1.5.5 魔法数字与字符串(Magic Values)

代码里到处都是 0、1、2 这样的数字,或者 "Pending"、"Success" 这样的字符串。没人知道它们代表什么。

// 坏味道示例
if (status == 2) // 2 是什么?没人知道
{
    // ...
}

重构思路:用枚举或常量代替。

总结一下:重构不是炫技,而是为了让代码更容易理解和修改。记住三个关键词:小步、测试、保持行为不变。下一章,咱们聊聊具体的重构手法,比如提取方法、移动字段、内联临时变量这些。

嗯,今天就先到这儿。如果你在 .NET 项目里也遇到过这些坏味道,别急,后面我会一步步带你搞定它们。