Skip to main content

重构 改善既有代码的设计 - Martin Fowler

Z0SJpe

《重构》第 2 版使用 JavaScript 作为示例语言,详细介绍了重构的原则、方法和具体手法。全书包含 60 多种重构手法,帮助开发者在不改变代码外部行为的前提下,持续改进代码的内部结构。

关于作者

Martin Fowler 是软件工程领域的权威专家:

  • ThoughtWorks 首席科学家:全球知名软件咨询公司
  • 敏捷开发先驱:与 Kent Beck 等人共同推动敏捷运动
  • UML 创始人之一:参与统一建模语言的创建
  • 技术作家:著有《企业应用架构模式》《分析模式》等经典著作
  • NoSQL 运动推动者:最早提出 NoSQL 概念

经典摘录

重构的定义:在不改变代码外部行为的前提下,改善其内部结构。

两次法则:第一次做某件事时只管去做;第二次再做类似的事情时可能会反感,但还是照做;第三次再做时,就应该重构了。

任何傻瓜都能写出计算机可以理解的代码。优秀的程序员写出的是人类能理解的代码。

当你需要添加新功能但代码结构不适合时,先重构代码使结构清晰,再添加新功能。

测试是重构的安全网。没有可靠的测试,就不要重构。

核心重构手法

1. 提炼函数 (Extract Function)

// 重构前
function printOwing(invoice) {
let outstanding = 0;
console.log('***********************');
console.log('**** Customer Owes ****');
console.log('***********************');
// 计算逾期金额
for (const o of invoice.orders) {
outstanding += o.amount;
}
// 记录到期日
const today = new Date();
invoice.dueDate = new Date(
today.getFullYear(),
today.getMonth(),
today.getDate() + 30
);
console.log(`name: ${invoice.customer}`);
console.log(`amount: ${outstanding}`);
console.log(`due: ${invoice.dueDate.toLocaleDateString()}`);
}

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

function printBanner() {
console.log('***********************');
console.log('**** Customer Owes ****');
console.log('***********************');
}

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

function recordDueDate(invoice) {
const today = new Date();
invoice.dueDate = new Date(
today.getFullYear(),
today.getMonth(),
today.getDate() + 30
);
}

function printDetails(invoice, outstanding) {
console.log(`name: ${invoice.customer}`);
console.log(`amount: ${outstanding}`);
console.log(`due: ${invoice.dueDate.toLocaleDateString()}`);
}

2. 以对象取代基本类型 (Replace Primitive with Object)

// 重构前
const order = {
customer: 'acme',
priority: 'high', // 字符串,容易拼写错误
};

function isHighPriority(order) {
return order.priority === 'high'; // 硬编码
}

// 重构后
class Priority {
constructor(value) {
this._value = value;
}

get value() {
return this._value;
}

get isHigh() {
return this._value === 'high';
}

static createHigh() {
return new Priority('high');
}

static createNormal() {
return new Priority('normal');
}

static createLow() {
return new Priority('low');
}
}

const order = {
customer: 'acme',
priority: Priority.createHigh(),
};

function isHighPriority(order) {
return order.priority.isHigh; // 使用对象方法
}

3. 保持接口完整 (Preserve Whole Object)

// 重构前:传递多个参数
function bookConcert(customer, theater, date, time, seats) {
// ...
}

// 重构后:传递对象
function bookConcert(bookingRequest) {
const { customer, theater, date, time, seats } = bookingRequest;
// ...
}

4. 以多态取代条件表达式 (Replace Conditional with Polymorphism)

// 重构前
class Bird {
get plumage() {
if (this._type === 'EuropeanSwallow') {
return 'average';
} else if (this._type === 'AfricanSwallow') {
return this._numberOfCoconuts > 2 ? 'tired' : 'average';
} else if (this._type === 'NorwegianParr') {
return 'scared';
}
return 'unknown';
}
}

// 重构后
class Bird {
get plumage() {
return 'unknown';
}
}

class EuropeanSwallow extends Bird {
get plumage() {
return 'average';
}
}

class AfricanSwallow extends Bird {
get plumage() {
return this._numberOfCoconuts > 2 ? 'tired' : 'average';
}
}

class NorwegianParr extends Bird {
get plumage() {
return 'scared';
}
}

重构与测试

// 重构的前提:可靠的测试
describe('Invoice', () => {
it('calculates outstanding correctly', () => {
const invoice = {
customer: 'acme',
orders: [
{ amount: 100 },
{ amount: 200 },
],
};
const result = calculateOutstanding(invoice);
expect(result).toBe(300);
});

it('handles empty orders', () => {
const invoice = {
customer: 'acme',
orders: [],
};
const result = calculateOutstanding(invoice);
expect(result).toBe(0);
});
});

读书心得

《重构》第 2 版最大的改进是使用 JavaScript 作为示例语言,让前端开发者更容易理解和实践。Fowler 在书中强调:

  1. 小步重构:每次只做小的改动,逐步积累
  2. 测试保障:重构前确保有可靠的测试
  3. 持续重构:重构不是一次性活动,而是日常习惯

书中 60 多种重构手法涵盖了从简单的"提炼函数"到复杂的"以多态取代条件表达式",每一种都有详细的步骤说明和代码示例。

对我影响最深的是两次法则:第一次做时只管去做,第二次可能还会忍受,第三次就必须重构了。这个简单的原则帮助我在实际工作中及时发现和消除代码异味。