您现在的位置是:网站首页 > 行为保持的重构方法文章详情

行为保持的重构方法

行为保持的重构方法

重构代码时保持原有行为是基本要求。行为保持意味着在不改变外部可见功能的前提下调整内部结构。JavaScript设计模式中,许多重构技巧都围绕这一原则展开。

提取函数

长函数难以维护时,将部分逻辑提取为独立函数是常见做法。关键要确保提取后的函数与原代码块行为完全一致。

// 重构前
function calculateTotal(items) {
  let total = 0;
  for (const item of items) {
    if (!item.discount) {
      total += item.price * item.quantity;
    } else {
      total += (item.price * 0.9) * item.quantity;
    }
  }
  return total;
}

// 重构后
function calculateItemCost(item) {
  return item.discount ? (item.price * 0.9) : item.price;
}

function calculateTotal(items) {
  return items.reduce((total, item) => {
    return total + calculateItemCost(item) * item.quantity;
  }, 0);
}

内联函数

与提取函数相反,当函数体与其名称同样清晰时,可以考虑内联:

// 重构前
function getRating(driver) {
  return moreThanFiveLateDeliveries(driver) ? 2 : 1;
}

function moreThanFiveLateDeliveries(driver) {
  return driver.numberOfLateDeliveries > 5;
}

// 重构后
function getRating(driver) {
  return driver.numberOfLateDeliveries > 5 ? 2 : 1;
}

替换算法

用更清晰的算法替换复杂实现时,必须确保新旧算法在所有边界条件下行为一致:

// 重构前
function findPeople(people) {
  const result = [];
  for (let i = 0; i < people.length; i++) {
    if (people[i].age >= 18) {
      result.push(people[i]);
    }
  }
  return result;
}

// 重构后
function findPeople(people) {
  return people.filter(person => person.age >= 18);
}

搬移函数

当函数与另一个类的交互比当前类更多时,考虑将其搬移:

// 重构前
class Order {
  constructor(customer) {
    this.customer = customer;
  }
  
  getDiscountRate() {
    return this.customer.getDiscountRate();
  }
}

class Customer {
  getDiscountRate() {
    // 计算折扣逻辑
  }
}

// 重构后
class Order {
  constructor(customer) {
    this.customer = customer;
  }
}

class Customer {
  getDiscountRate() {
    // 计算折扣逻辑
  }
  
  applyDiscount(order) {
    return order.total * this.getDiscountRate();
  }
}

封装变量

将可变数据封装起来,通过访问函数控制修改:

// 重构前
let defaultOwner = { firstName: "Martin", lastName: "Fowler" };

// 重构后
let defaultOwnerData = { firstName: "Martin", lastName: "Fowler" };

export function getDefaultOwner() {
  return Object.assign({}, defaultOwnerData);
}

export function setDefaultOwner(arg) {
  defaultOwnerData = arg;
}

以查询取代临时变量

消除临时变量可以使函数更清晰:

// 重构前
function calculateTotal() {
  const basePrice = quantity * itemPrice;
  if (basePrice > 1000) {
    return basePrice * 0.95;
  }
  return basePrice * 0.98;
}

// 重构后
function calculateTotal() {
  if (basePrice() > 1000) {
    return basePrice() * 0.95;
  }
  return basePrice() * 0.98;
}

function basePrice() {
  return quantity * itemPrice;
}

引入参数对象

将相关参数组合为对象提高可读性:

// 重构前
function amountInvoiced(startDate, endDate) { /* ... */ }
function amountReceived(startDate, endDate) { /* ... */ }
function amountOverdue(startDate, endDate) { /* ... */ }

// 重构后
function amountInvoiced(dateRange) { /* ... */ }
function amountReceived(dateRange) { /* ... */ }
function amountOverdue(dateRange) { /* ... */ }

分解条件表达式

复杂条件逻辑可以分解为独立函数:

// 重构前
if (!aDate.isBefore(plan.summerStart) && !aDate.isAfter(plan.summerEnd)) {
  charge = quantity * plan.summerRate;
} else {
  charge = quantity * plan.regularRate + plan.regularServiceCharge;
}

// 重构后
if (summer()) {
  charge = summerCharge();
} else {
  charge = regularCharge();
}

function summer() {
  return !aDate.isBefore(plan.summerStart) && !aDate.isAfter(plan.summerEnd);
}

function summerCharge() {
  return quantity * plan.summerRate;
}

function regularCharge() {
  return quantity * plan.regularRate + plan.regularServiceCharge;
}

合并重复的条件片段

将重复代码移出条件分支:

// 重构前
if (isSpecialDeal()) {
  total = price * 0.95;
  send();
} else {
  total = price * 0.98;
  send();
}

// 重构后
if (isSpecialDeal()) {
  total = price * 0.95;
} else {
  total = price * 0.98;
}
send();

以多态取代条件表达式

当条件逻辑基于类型时,考虑使用多态:

// 重构前
function getSpeed(animal) {
  switch (animal.type) {
    case "european":
      return animal.baseSpeed;
    case "african":
      return animal.baseSpeed - animal.loadFactor * animal.numberOfCoconuts;
    case "norwegian":
      return (animal.isNailed) ? 0 : animal.baseSpeed - animal.loadFactor;
  }
}

// 重构后
class European {
  getSpeed() {
    return this.baseSpeed;
  }
}

class African {
  getSpeed() {
    return this.baseSpeed - this.loadFactor * this.numberOfCoconuts;
  }
}

class Norwegian {
  getSpeed() {
    return (this.isNailed) ? 0 : this.baseSpeed - this.loadFactor;
  }
}

引入断言

明确表达代码假设:

function applyDiscount(order) {
  if (!order.customer) return 0;
  // 断言客户必须有折扣率
  console.assert(order.customer.discountRate !== undefined, "Customer must have discount rate");
  return order.total * order.customer.discountRate;
}

保持函数单一职责

每个函数只做一件事:

// 重构前
function processOrder(order) {
  validateOrder(order);
  calculateTotal(order);
  saveOrder(order);
  sendConfirmation(order);
}

// 重构后
function processOrder(order) {
  validateOrder(order);
  const total = calculateTotal(order);
  const savedOrder = saveOrder(order, total);
  sendConfirmation(savedOrder);
}

参数化函数

合并相似函数时,用参数区分不同行为:

// 重构前
function tenPercentRaise(person) {
  person.salary = person.salary.multiply(1.1);
}

function fivePercentRaise(person) {
  person.salary = person.salary.multiply(1.05);
}

// 重构后
function raise(person, factor) {
  person.salary = person.salary.multiply(1 + factor);
}

移除标记参数

用显式函数替代布尔参数:

// 重构前
function setDimension(name, value, isWidth) {
  if (isWidth) {
    this.width = value;
  } else {
    this.height = value;
  }
}

// 重构后
function setWidth(value) {
  this.width = value;
}

function setHeight(value) {
  this.height = value;
}

保持对象完整性

传递整个对象而非多个属性:

// 重构前
function calculateCost(basePrice, discountRate, taxRate) {
  return basePrice * (1 - discountRate) * (1 + taxRate);
}

// 重构后
function calculateCost(pricing) {
  return pricing.basePrice * (1 - pricing.discountRate) * (1 + pricing.taxRate);
}

替换魔法字面量

用常量替代魔法数字/字符串:

// 重构前
function potentialEnergy(mass, height) {
  return mass * 9.81 * height;
}

// 重构后
const GRAVITATIONAL_CONSTANT = 9.81;

function potentialEnergy(mass, height) {
  return mass * GRAVITATIONAL_CONSTANT * height;
}

组合替代继承

优先使用对象组合而非类继承:

// 重构前
class Employee extends Person {
  // 继承所有Person方法
}

// 重构后
class Employee {
  constructor(person) {
    this.person = person;
  }
  
  getName() {
    return this.person.getName();
  }
}

延迟加载

需要时才初始化属性:

class Customer {
  get orders() {
    return this._orders || this.fetchOrders();
  }
  
  fetchOrders() {
    this._orders = /* 从数据库加载 */;
    return this._orders;
  }
}

引入Null对象

避免null检查:

// 重构前
if (customer === null) {
  plan = BillingPlan.basic();
} else {
  plan = customer.billingPlan;
}

// 重构后
class NullCustomer {
  get billingPlan() {
    return BillingPlan.basic();
  }
}

plan = customer.billingPlan;

提炼类

当一个类承担过多责任时:

// 重构前
class Person {
  constructor() {
    this.officeAreaCode = "";
    this.officeNumber = "";
  }
  
  getTelephoneNumber() {
    return `(${this.officeAreaCode}) ${this.officeNumber}`;
  }
}

// 重构后
class TelephoneNumber {
  constructor(areaCode, number) {
    this.areaCode = areaCode;
    this.number = number;
  }
  
  toString() {
    return `(${this.areaCode}) ${this.number}`;
  }
}

class Person {
  constructor() {
    this.telephoneNumber = new TelephoneNumber("", "");
  }
  
  getTelephoneNumber() {
    return this.telephoneNumber.toString();
  }
}

隐藏委托关系

减少客户端对中间对象的依赖:

// 重构前
client.department.manager = newManager;

// 重构后
class Client {
  setManager(manager) {
    this.department.manager = manager;
  }
}

引入本地扩展

为第三方类添加方法:

// 原始Date类没有nextDay方法
Date.prototype.nextDay = function() {
  return new Date(this.getTime() + 24 * 60 * 60 * 1000);
};

// 或者使用包装类
class MyDate {
  constructor(date) {
    this.date = date;
  }
  
  nextDay() {
    return new Date(this.date.getTime() + 24 * 60 * 60 * 1000);
  }
}

以命令取代函数

将函数封装为对象,支持撤销等操作:

class ChargeCommand {
  constructor(customer, amount) {
    this.customer = customer;
    this.amount = amount;
  }
  
  execute() {
    this.customer.balance += this.amount;
  }
  
  undo() {
    this.customer.balance -= this.amount;
  }
}

保持测试不变

重构时要确保测试继续通过:

// 重构前测试
test("calculates total", () => {
  const items = [{ price: 10, quantity: 2 }];
  expect(calculateTotal(items)).toBe(20);
});

// 重构后同样的测试应该继续通过

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步