
重构技巧:在不改变行为的前提下改进代码
重构是在不改变外部行为的前提下调整代码结构。始终在有测试覆盖的情况下进行重构。
提取函数(Extract Function)
// 重构前
function printOwing(invoice: Invoice): void {
let outstanding = 0;
console.log('***********************');
console.log('**** Customer Owes ****');
console.log('***********************');
for (const order of invoice.orders) {
outstanding += order.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}`);
}
// 重构后
function printOwing(invoice: Invoice): void {
printBanner();
const outstanding = calculateOutstanding(invoice);
recordDueDate(invoice);
printDetails(invoice, outstanding);
}
function printBanner(): void {
console.log('***********************');
console.log('**** Customer Owes ****');
console.log('***********************');
}
function calculateOutstanding(invoice: Invoice): number {
return invoice.orders.reduce((sum, order) => sum + order.amount, 0);
}
function recordDueDate(invoice: Invoice): void {
const today = new Date();
invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30);
}
引入参数对象(Introduce Parameter Object)
// 重构前:多个相关参数
function createDateRange(startYear: number, startMonth: number, startDay: number,
endYear: number, endMonth: number, endDay: number): void { }
// 重构后:将参数组合成对象
interface DateRange {
start: Date;
end: Date;
}
function createDateRange(range: DateRange): void { }
用多态替换条件逻辑(Replace Conditional with Polymorphism)
// 重构前:基于类型的 switch 语句
function getSpeed(animal: Animal): number {
switch (animal.type) {
case 'bird': return animal.airSpeed;
case 'horse': return animal.runSpeed;
case 'fish': return animal.swimSpeed;
default: throw new Error('Unknown animal');
}
}
// 重构后:多态
abstract class Animal {
abstract getSpeed(): number;
}
class Bird extends Animal {
getSpeed(): number { return this.airSpeed; }
}
class Horse extends Animal {
getSpeed(): number { return this.runSpeed; }
}
class Fish extends Animal {
getSpeed(): number { return this.swimSpeed; }
}
用命名常量替换魔法数字(Replace Magic Numbers with Named Constants)
// 重构前
function calculateDiscount(price: number, days: number): number {
if (days > 90) return price * 0.15;
if (days > 30) return price * 0.1;
return 0;
}
// 重构后
const SENIOR_CUSTOMER_DAYS = 90;
const REGULAR_CUSTOMER_DAYS = 30;
const SENIOR_DISCOUNT_RATE = 0.15;
const REGULAR_DISCOUNT_RATE = 0.1;
function calculateDiscount(price: number, daysSinceRegistration: number): number {
if (daysSinceRegistration > SENIOR_CUSTOMER_DAYS) return price * SENIOR_DISCOUNT_RATE;
if (daysSinceRegistration > REGULAR_CUSTOMER_DAYS) return price * REGULAR_DISCOUNT_RATE;
return 0;
}
卫语句(Guard Clauses)
// 重构前:嵌套条件
function processPayment(payment: Payment): void {
if (payment !== null) {
if (payment.amount > 0) {
if (!payment.isExpired()) {
// 实际处理逻辑深埋在此
chargeCard(payment);
}
}
}
}
// 重构后:提前返回 / 卫语句
function processPayment(payment: Payment): void {
if (!payment) return;
if (payment.amount <= 0) return;
if (payment.isExpired()) throw new Error('Payment method expired');
chargeCard(payment);
}
移动字段 / 提取类(Move Field / Extract Class)
// 重构前:Person 有太多电话相关字段
class Person {
name: string;
officePhoneNumber: string;
officeAreaCode: string;
homePhoneNumber: string;
homeAreaCode: string;
}
// 重构后:提取 TelephoneNumber 类
class TelephoneNumber {
constructor(
readonly areaCode: string,
readonly number: string
) {}
toString(): string { return `(${this.areaCode}) ${this.number}`; }
}
class Person {
name: string;
officePhone: TelephoneNumber;
homePhone: TelephoneNumber;
}
成功重构的关键在于小步安全地进行,并始终有可靠的测试套件作为后盾。