5重构(第2版)

介绍重构目录

如何使用重构目录

重构目录重构手法

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第5章:介绍重构目录(Introducing the Refactoring Catalog)

一、导读

1.1 本章学习目标

第5章是《重构》的工具箱指南。Martin Fowler 在本章中介绍了重构目录的使用方法——每个重构手法的描述格式、如何查阅目录、如何选择合适的手法。掌握重构目录,你就拥有了一套系统化的代码改善工具。

本章的核心学习目标包括:

  • 理解重构手法的描述格式:名称、动机、做法、示例、变体
  • 掌握查阅重构目录的方法:如何快速找到适合当前场景的手法
  • 学会组合使用多种重构手法:单个手法往往不够,组合使用效果更佳
  • 理解重构手法的适用场景和限制:不是所有手法都适用于所有情况
  • 建立重构手法的直觉:看到代码问题就知道该用什么手法

1.2 为什么本章如此重要

重构不是随意的代码修改,而是有章可循的系统化改善。重构目录就是这种系统化的体现——它将多年的实践经验提炼为可复用、可传授的手法。就像医学中的手术步骤一样,每个重构手法都有明确的步骤、预期效果和注意事项。

掌握重构目录的价值在于:当你面对一段"坏代码"时,你不需要从零开始思考如何改善——你可以查阅目录,找到已经验证过的手法,按照步骤执行。这极大地提高了重构的效率和安全性。

1.3 核心问题引导

重构手法的描述格式是什么?每个部分有什么作用?

如何选择合适的重构手法?

重构手法之间有什么关联?如何组合使用?

重构手法的"做法"为什么如此详细?能否跳过某些步骤?

如何判断一个重构手法是否适用于当前场景?


二、核心概念详解

2.1 重构手法的描述格式

Fowler 为每个重构手法定义了统一的描述格式:

  • 名称(Name):手法的简洁描述,如"Extract Function"
  • 动机(Motivation):为什么要做这个重构?什么场景下使用?
  • 做法(Mechanics):详细的步骤说明,确保每一步都是安全的
  • 示例(Example):重构前后的代码对比
  • 变体(Variations):手法在不同编程语言或场景下的变化
markdown
## Extract Function(提炼函数)

### 动机
当你看到一段代码可以放在一起被一个名称注释时,就把它提取到一个独立的函数中。

### 做法
1. 创建一个新函数,以它的意图命名
2. 将需要提取的代码复制到新函数中
3. 检查是否有局部变量需要作为参数传递
4. 在原位置用函数调用替换提取的代码
5. 运行测试

### 示例
// 重构前
function printOwing(invoice) {
  printBanner();
  // calculate outstanding
  let outstanding = 0;
  for (const o of invoice.orders) {
    outstanding += o.amount;
  }
  printDetails(outstanding);
}

// 重构后
function printOwing(invoice) {
  printBanner();
  const outstanding = calculateOutstanding(invoice);
  printDetails(outstanding);
}

function calculateOutstanding(invoice) {
  let outstanding = 0;
  for (const o of invoice.orders) {
    outstanding += o.amount;
  }
  return outstanding;
}

2.2 如何查阅重构目录

Fowler 的重构目录按照坏味道组织——当你发现某种坏味道时,可以查阅对应的重构手法:

坏味道推荐的重构手法
神秘命名Change Function Declaration, Rename Variable
重复代码Extract Function, Slide Statements
过长函数Extract Function, Replace Temp with Query
过长参数列Introduce Parameter Object, Remove Flag Argument
发散式变化Extract Class, Move Function
霰弹式修改Move Function, Move Field, Inline Class

2.3 重构手法的组合使用

在实际重构中,很少只使用一个手法。通常是多个手法的组合:

javascript
// 初始代码:多种坏味道混合
function process(data) {
  let result = '';
  for (let i = 0; i < data.length; i++) {
    if (data[i].type === 'A') {
      result += data[i].value * 1.1;
    } else if (data[i].type === 'B') {
      result += data[i].value * 0.9;
    }
  }
  return result;
}

// 组合使用多个重构手法
// 1. Extract Function:提取计算逻辑
// 2. Replace Conditional with Polymorphism:用多态替代条件
// 3. Move Function:将逻辑移到合适的类

// 步骤1:提取函数
function process(data) {
  return data.reduce((sum, item) => sum + calculateItem(item), 0);
}

function calculateItem(item) {
  if (item.type === 'A') return item.value * 1.1;
  if (item.type === 'B') return item.value * 0.9;
  return item.value;
}

// 步骤2:用多态替代条件
class ItemProcessor {
  calculate(item) { return item.value; }
}

class TypeAProcessor extends ItemProcessor {
  calculate(item) { return item.value * 1.1; }
}

class TypeBProcessor extends ItemProcessor {
  calculate(item) { return item.value * 0.9; }
}

function process(data) {
  return data.reduce((sum, item) => {
    const processor = getProcessor(item.type);
    return sum + processor.calculate(item);
  }, 0);
}

2.4 重构手法的分类

Fowler 将重构手法分为几大类:

2.4.1 封装类

  • Extract Function / Inline Function
  • Rename Variable / Extract Variable
  • Encapsulate Record / Encapsulate Collection

2.4.2 搬移类

  • Move Function / Move Field
  • Move Statements into Function / Move Statements to Callers

2.4.3 数据组织类

  • Split Variable / Remove Assignments to Parameters
  • Replace Derived Data with Updater
  • Change Reference to Value / Change Value to Reference

2.4.4 条件逻辑类

  • Decompose Conditional
  • Replace Conditional with Polymorphism
  • Introduce Special Case
  • Replace Conditional with State/Strategy

2.4.5 API 类

  • Separate Query from Modifier
  • Parameterize Function
  • Remove Setting Method

2.4.6 继承类

  • Pull Up Field / Pull Up Method
  • Push Down Method / Push Down Field
  • Replace Inheritance with Delegation

三、实战案例

3.1 案例:使用重构目录改善代码

以下是一个实际的重构过程,展示了如何查阅目录并组合使用多种手法。

javascript
// 初始代码:包含多种坏味道
class OrderService {
  processOrder(order) {
    let total = 0;
    for (const item of order.items) {
      if (item.type === 'ELECTRONICS') {
        total += item.price * 1.13;
      } else if (item.type === 'BOOK') {
        total += item.price;
      } else if (item.type === 'FOOD') {
        total += item.price * 1.05;
      }
    }
    
    if (order.customer.isVIP) {
      total *= 0.9;
    }
    
    if (total > 100) {
      total -= 10;
    }
    
    return { total, status: 'COMPLETED' };
  }
}

// 重构过程:
// 1. 识别坏味道:过长函数、条件逻辑复杂
// 2. 查阅目录:Decompose Conditional, Extract Function
// 3. 逐步重构

class OrderService {
  processOrder(order) {
    const subtotal = this.calculateSubtotal(order.items);
    const discount = this.calculateDiscount(order.customer, subtotal);
    const total = subtotal - discount;
    return { total, status: 'COMPLETED' };
  }
  
  calculateSubtotal(items) {
    return items.reduce((sum, item) => sum + this.calculateItemPrice(item), 0);
  }
  
  calculateItemPrice(item) {
    const taxRate = this.getTaxRate(item.type);
    return item.price * taxRate;
  }
  
  getTaxRate(type) {
    const rates = {
      'ELECTRONICS': 1.13,
      'BOOK': 1.0,
      'FOOD': 1.05
    };
    return rates[type] || 1.0;
  }
  
  calculateDiscount(customer, subtotal) {
    let discount = 0;
    if (customer.isVIP) discount += subtotal * 0.1;
    if (subtotal > 100) discount += 10;
    return discount;
  }
}

四、本章小结

第5章介绍了重构目录的使用方法:

重构手法有统一的描述格式:名称、动机、做法、示例、变体

目录按坏味道组织:发现问题就能找到对应的手法

手法可以组合使用:多个手法配合解决复杂问题

手法分为几大类:封装、搬移、数据组织、条件逻辑、API、继承

做法步骤必须遵循:每一步都是经过验证的安全步骤

本章的核心思想是:重构是一门手艺,而不是一门艺术。它有章可循,有据可查,可以通过学习和练习来掌握。


五、思考与练习

思考题

重构手法与编程模式有什么关系?

为什么重构手法的"做法"要如此详细?

如何判断一个重构手法是否适用于当前的编程语言?

重构目录是否需要随编程语言的发展而更新?

编程练习

选择 3 个不同的重构手法,分别在实际代码中练习。

尝试组合使用 3 个以上的重构手法来改善一段复杂代码。

为你团队常用的编程语言编写一份"重构手法速查表"。


5/12介绍重构
0%