代码重构完全指南:从识别坏味道到 Strangler Fig 大重构

重构(Refactoring)是 Martin Fowler 1999 年定义的:"在不改变软件外部行为的前提下,改善其内部结构"。日常开发里,重构和写新功能同样重要 —— 没有持续重构,代码债务会让项目越走越慢。这篇文章把重构的核心手法、识别坏味道、安全重构的工程实践一次讲透。

什么时候重构

Martin Fowler 的"露营规则":离开时让代码比来时更干净一点。具体时机:

  • 写新功能时,顺手清理碰到的乱代码。
  • 修 bug 时,先重构出可测试的结构,再修。
  • Code Review 发现重复代码 / 坏命名。
  • 新人看不懂老代码 —— 那是重构信号。

不要:专门花一个 sprint"大重构",几乎都会失败 —— 因为业务不停,大重构的 PR review 不动、合并冲突频繁、上线风险高。小步快走是重构的核心节奏。

代码坏味道(Code Smells)

1. 重复代码(Duplicated Code)

同样逻辑在多处出现。改一处忘了改另一处 = bug。重构手法:提取函数(Extract Function)、提取类(Extract Class)。

2. 长函数(Long Function)

50 行以上的函数都该警惕。一般规则:函数应该能在一屏内看完(20-30 行)。重构:把每个"做一件事"的代码块提取成子函数。

// 长函数
function processOrder(order) {
    // 验证 (20 行)
    if (!order.items.length) throw new Error('empty');
    for (const item of order.items) { ... }
    if (order.total <= 0) throw new Error('zero total');

    // 计算 (15 行)
    let total = 0;
    for (const item of order.items) { ... }
    let tax = 0; ...
    let shipping = 0; ...

    // 持久化 (10 行)
    db.transaction(() => { ... });

    // 通知 (10 行)
    emailService.send(...);
    smsService.send(...);
}

// 重构后
function processOrder(order) {
    validate(order);
    const totals = calculateTotals(order);
    persist(order, totals);
    notify(order);
}

function validate(order) { ... }
function calculateTotals(order) { ... }
function persist(order, totals) { ... }
function notify(order) { ... }

3. 大类(Large Class)

一个类几千行,什么都做。违反 SRP。重构:按职责拆分。

4. 长参数列表(Long Parameter List)

// 太多参数
function createUser(name, email, age, city, country, phone, role, active, ...) {}

// 重构 1:参数对象
function createUser(params: CreateUserParams) {}

// 重构 2:Builder 模式
const user = User.builder()
    .name(...).email(...).build();

5. 发散式变化(Divergent Change)

"因为数据库换了改这个类,因为 UI 换了也改这个类"—— 这个类有多个变化原因,违反 SRP。重构:按变化原因拆分。

6. 霰弹式修改(Shotgun Surgery)

改一个小需求要改十几个文件。重构:把相关代码集中到一处。

7. Feature Envy

方法 A 里大量使用类 B 的字段 —— 说明 A 应该在 B 里。重构:Move Method。

8. 数据泥团(Data Clumps)

3 个字段总一起出现。重构:把它们封装成对象。

// 三个字段总一起传
function shipTo(street, city, country) { ... }
function billTo(street, city, country) { ... }

// 重构成 Address 类
class Address { constructor(public street, public city, public country) {} }
function shipTo(addr: Address) { ... }
function billTo(addr: Address) { ... }

9. 命名糟糕

fn1()doStuff()helper(data)。重命名是最便宜也最有价值的重构。

核心重构手法

Extract Function

从长函数里抽出一段命名清晰的子函数。最常用,几乎天天做。

Inline Function

反向操作:函数太简单,内联回去比单独命名清晰。

Move Method / Move Field

方法 / 字段挪到更合适的类。Feature Envy 的标准修复。

Extract Class

大类拆出新类,把相关字段方法搬过去。

Rename

改变量 / 方法 / 类名。IDE 一键完成,但要审查使用点。

Replace Conditional with Polymorphism

把 switch / if-else 链替换成多态。

// 重构前
function area(shape) {
    switch (shape.type) {
        case 'circle': return Math.PI * shape.r * shape.r;
        case 'square': return shape.side * shape.side;
        case 'triangle': return 0.5 * shape.base * shape.height;
    }
}

// 重构后
abstract class Shape { abstract area(): number; }
class Circle extends Shape {
    constructor(public r: number) { super(); }
    area() { return Math.PI * this.r * this.r; }
}
class Square extends Shape {
    constructor(public side: number) { super(); }
    area() { return this.side * this.side; }
}

Introduce Parameter Object

把多个相关参数合并成一个对象。Data Clumps 的修复。

安全重构的工程实践

1. 重构前先有测试

没有测试的代码不能重构 —— 改完不知道有没有改坏。"特征测试"(Characterization Tests):不评判代码对错,只记录当前行为。重构后这些测试应该全过 —— 即使原行为是 bug。先稳住现状,再渐进改正。

2. 一次只做一种重构

不要边重构边加功能。每次 PR 标清楚是"重构"(行为不变)还是"功能改动"。混在一起 review 极痛苦。

3. 小步提交

每完成一种重构手法就 commit,而不是攒一周再 PR。出问题能精确定位到哪一步。

4. 用 IDE 自动化

Rename、Extract Method、Move Method 这些操作 IDE 都自动且安全(IntelliJ / VSCode / Visual Studio)。手动改容易漏点。

大型重构的策略

Strangler Fig 模式

老代码不能一次替换,逐步用新代码"绞杀"老的:

  1. 新代码并行实现新版本。
  2. 把请求逐步路由到新版(灰度发布)。
  3. 全部流量切到新版后,删老代码。

这种"分支并行"模式让大型重构变成可观测、可回滚的工程任务。Martin Fowler 用"绞杀榕"植物比喻 —— 慢慢从外面包裹老树,等老树死了榕树独立。

Branch by Abstraction

先在老代码上做抽象(接口),老实现仍然在,新实现可以并行加。然后逐步切。比 Strangler Fig 更细粒度。

重构和性能

Fowler 的观点:先重构,再 profile,再优化。理由:

  • 重构后的代码更易看懂瓶颈在哪。
  • 过早优化(premature optimization)往往优化错地方。
  • 清晰的结构让"真正需要优化时"更容易做。

但要分情况:热点路径(每秒百万次)的明显性能问题,可以优化先于重构。

重构什么时候停

  • 当下需求不需要这部分代码改 —— 不要"顺手"重构无关代码,扩大 PR 范围。
  • 代码已经清楚明白 —— 继续重构边际收益低。
  • 没有测试覆盖 —— 风险高于收益。

团队层面的重构文化

  1. 把"重构"作为正常工作的一部分,不要"等到有时间"。
  2. Code Review 时主动指出可重构的地方
  3. 建立"探测"机制:Linter / SonarQube 自动检测代码坏味道。
  4. 有重构预算:每个 sprint 留 10-20% 时间给重构和技术债。
  5. 新人入职任务里包含一次重构:既熟悉代码又留下贡献。

写在最后

重构不是"大动作",是日常习惯。每天写新代码时,留意身边的坏味道,顺手清理一点。三个月后回头看,整个代码库都干净了。这种"积小成多"的力量比"大重构"更可靠 —— 大重构通常会被业务打断,小重构嵌入日常工作流不会被打断。把重构变成肌肉记忆,是工程师成熟的标志。

一图看懂

Strangler Fig 重构流程一图看懂:

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
技术教程

Clean Architecture 完全指南:从依赖倒置到端口与适配器

2026-5-15 17:43:37

技术教程

Code Review 完全指南:从基本原则到团队规范化

2026-5-15 17:43:38

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索