正在加载,请稍候…

重构技巧:在不改变行为的前提下改进代码

学习系统化的重构技巧来改进代码结构,包括提取函数、引入参数对象、用多态替换条件逻辑等,每一步都有测试保障。

重构技巧:在不改变行为的前提下改进代码

重构技巧:在不改变行为的前提下改进代码

重构是在不改变外部行为的前提下调整代码结构。始终在有测试覆盖的情况下进行重构。

提取函数(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;
}

成功重构的关键在于小步安全地进行,并始终有可靠的测试套件作为后盾。