重构

《重构》- 简化函数调用

2018-11-20  本文已影响4人  nimw

一. Rename Method(函数改名)

介绍

  1. 场景
    函数的名称未能揭示函数的用途。
  2. 手法
    修改函数名称。

动机

  1. 给函数命名有一个好方法:首先考虑应该给这个函数写上一句怎样的注释,然后想办法将注释编程函数名称。
  2. 如果你看到一个函数名称不能很好地传达它的用途,应该马上加以修改。
  3. 你的代码首先是为人写的,其次才是为计算机写的。而人需要良好名称的函数。

范例

重构前

getTelephoneNumber (){
  return `(${_officeAreaCode}-${_officeNumber})`
}

重构后

getOfficeTelephoneNumber (){
  return `(${_officeAreaCode}-${_officeNumber})`
}

二. Add Parameter(添加参数)

介绍

  1. 场景
    某个函数需要从调用端得到更多信息。
  2. 手法
    为此函数添加一个对象参数,让该对象带进函数所需信息。

动机

  1. 除了添加参数外,你常常还有其他选择。只要可能,其他选择都比添加参数要好,因为他们不会增加参数列的长度。
  2. 过长的参数列是不好的味道,因为程序员很难记住那么多参数。
  3. 并非禁止添加参数,但是在添加参数之前需要了解是否有其他选择。

三. Remove Parameter(移除参数)

介绍

  1. 场景
    函数本体不再需要某个参数。
  2. 手法
    将该参数去除。

动机

  1. 程序员可能经常添加参数,却往往不愿意去掉他们。
  2. 参数代表着函数所需的信息,不同的参数值有不同的意义,应及时去掉多余参数。

四. Separate Query form Modifier(将查询函数和修改函数分离)

介绍

  1. 场景
    某个函数既返回对象状态值,又修改对象状态。
  2. 手法
    建立两个不同的函数,其中一个负责查询,另一个负责修改。

动机

  1. 任何有返回值的函数,都不应该有看得到的副作用。
  2. 如果你遇到一个“既有返回值又有副作用”的函数,就应该试着将查询动作从修改动作中分割出来。

范例

重构前

foundMiscreant(people) {
  for(let i = 0; i < people.length; i++) {
    if(people[i] === 'Don' || peoplep[i] === 'John') {
      this.sendAlert()
      return 'Dom'
    }
  }
  return ''
}

checkSecurity(people) {
  const found = this.foundMiscreant(people)
  this.someLaterCode(found)
}

重构后

foundPerson(people) {
  for(let i = 0; i < people.length; i++) {
    if(people[i] === 'Don' || peoplep[i] === 'John') {
      return 'Dom'
    }
  }
  return ''
}

alertPerson(people) {
  if(this.foundPerson(people)) {
    this.sendAlert()
  }
}

checkSecurity(people) {
  this.alertPerson(people)
  const found = this.foundPerson(people)
  this.someLaterCode(found)
}

五. Parameterize Method(令函数携带参数)

介绍

  1. 场景
    若干函数做了类似的工作,但在函数本体中却包含了不同的值。
  2. 手法
    建立单一函数,以参数表达那些不同的值。

动机

  1. 你可能发现这样的两个函数:他们做着类似的工作,但因为少数几个值致使行为略有不同。
  2. 你可以将这些各自分离的函数统一起来,并通过参数来处理那些变化,用以简化问题。
  3. 本项重构的要点在于:以“可将少量数据视为参数”为依据,找出带有重复性的代码。

范例

重构前

baseCharge() {
  let result = Math.min(this.lastUsage(), 100) * 0.03
  if(this.lastUsage() > 100) {
    result += (Math.min(this.lastUsage(), 200) -100) * 0.05
  }
  if(this.lastUsage() > 200) {
    result += (this.lastUsage() - 200) * 0.07
  }
  return new Dollars(result)
}

重构后

baseCharge() {
  let result = this.usageInRange(0, 100) * 0.03
  result += this.usageInRange(100, 200) + 0.05
  result += this.usageInRange(200, Number.MAX_SAFE_INTEGER)
  return new Dollars(result)
}

usageInRange(start, end) {
  return this.lastUsage() > start ? Math.min(this.lastUsage(), end) - start : 0
}

六. Replace Parameter with Explicit Methods(以明确函数取代参数)

介绍

  1. 场景
    你有一个函数,其中完全取决于参数值而采取不同行为。
  2. 手法
    针对该参数的每一个可能值,建立一个独立函数。

动机

  1. 如果某个参数有多种可能的值,而函数内又以条件表达式检查这些参数值,并根据不同参数值做出不同的行为,那么就应该使用本项重构。
  2. 提供不同的函数给调用者使用,可以避免出现条件表达式。
  3. 本项重构可以获取一个更清晰的接口,哪怕只是给一个内部的布尔变量赋值,Switch.beOn()也比Switch.setState(true)要清晰的多。

范例

重构前

class Employee {
  static ENGINEER = 0;
  static SALESMAN = 1;
  static MANAGER = 2;

  static create(type) {
    switch(type) {
      case Employee.ENGINEER:
        return new Engineer()
      case Employee.SALESMAN:
        return new Salesman()
      case Employee.MANAGER:
        return new Manager()
      default:
        throw new Error('Incorrect type value')
    }
  }
}

const e = Employee.create(Employee.ENGINEER)

重构后

class Employee {
  static createEngineer() {
    return new Engineer()
  }

  static createSalesman() {
    return new Salesman()
  }

  static createManager() {
    return new Manager()
  }
}

const e = Employee.createEngineer()

七. Preserve Whole Object(保持对象完整)

介绍

  1. 场景
    你从某个对象中取出若干值,将他们作为某一次函数调用时的参数。
  2. 手法
    改为传递整个对象。

范例

重构前

class Room {
  withinPlan(plan) {
    const low = this.daysTempRange().getLow()
    const high = this.daysTempRange().getHigh()
    return plan.withinRange(low, high)
  }
}

class HeatingPlan {
  withinRange(low, high) {
    return low >= this._range.getLow() && high <= this._range.getHigh()
  }
}

重构后

class Room {
  withinPlan(plan) {
    return plan.withinRange(this.daysTempRange())
  }
}

class HeatingPlan {
  withinRange(arg) {
    return arg.getLow() >= this._range.getLow() && arg.getHigh() <= this._range.getHigh()
  }
}

八. Replace Parameter with Methods(以函数取代参数)

介绍

  1. 场景
    对象调用某个函数,并将所得结果作为参数,传递给另一个函数。而接受该参数的函数本身也能够调用前一个函数。
  2. 手法
    让参数接受者去除该项参数,并直接调用前一个函数。

动机

  1. 如果函数可以通过其他途径获得参数值,那么他就不应该通过参数获取该值。

范例

重构前

getPrice() {
  const basePrice = this._quantity * this._itemPrice
  const discountLevel = this._quantity > 100 ? 2 : 1
  return this.discountPrice(basePrice, discountLevel)
}

discountPrice(basePrice, discountLevel) {
  return discountLevel === 2 ? basePrice * 0.1 : basePrice * 0.05
}

重构后

getPrice() {
  return this.getDiscountLevel() === 2 ? this.getBasePrice() * 0.1 : this.getBasePrice() * 0.05
}

getBasePrice() {
  return this._quantity * this._itemPrice
}

getDiscountLevel() {
  return this._quantity > 100 ? 2 : 1
}

九. Introduce Parameter Object(引入参数对象)

介绍

  1. 场景
    某些参数总是很自然地同时出现。
  2. 手法
    以一个对象取代这些参数。

动机

  1. 经常看到特定的一组参数总是一起被传递。可能有好几个函数都使用这一组参数,这些函数可能隶属与同一个类,也可能隶属于不同的类。
  2. 这样的一组参数就是所谓的数据泥团,我们可以运用一个对象包装所有这些数据,再以该对象取代他们。

范例

重构前

class Entry{
  constructor(value, chargeDate) {
    this._value = value
    this._chargeDate = chargeDate
  }

  getDate() {
    return this._chargeDate
  }

  getValue() {
    return this._value
  }
}

class Account{
  getFlowBetween(start, end) {
    let result = 0
    this._entries.forEach(entry => {
      if(entry.getDate().equals(start) || entry.getDate().equals(end) || (entry.getDate().after(start) && entry.getDate().before(end))) {
        result += entry.getValue()
      }
    })
    return result
  }
}

const flow = anAccount.getFlowBetween(startDate, endDate)

重构后

class Entry{
  constructor(value, chargeDate) {
    this._value = value
    this._chargeDate = chargeDate
  }

  getDate() {
    return this._chargeDate
  }

  getValue() {
    return this._value
  }
}

class DateRange {
  constructor(start, end) {
    this._start = start
    this._end = end
  }

  getStart() {
    return this._start
  }

  getEnd() {
    return this._end
  }

  includes(arg) {
    return arg.equals(this._start) || 
    arg.equals(this._end) || 
    (arg.after(this._start) && arg.before(this._end))
  }
}

class Account{
  getFlowBetween(range) {
    let result = 0
    this._entries.forEach(entry => {
      if(range.includes(entry.getDate())){
        result += entry.getValue()
      }
    })
    return result
  }
}

const flow = anAccount.getFlowBetween(new DateRange(startDate, endDate))

十. Remove Setting Method(移除设置函数)

介绍

  1. 场景
    类中的某个字段应该在对象创建时被设值,然后就不再改变。
  2. 手法
    去掉该字段的所有设值函数。

范例

重构前

class Account {
  constructor(id) {
    this.setId(id)
  }

  setId(arg) {
    this._id = arg
  }
}

重构后

class Account {
  constructor(id) {
    this._id = id
  }
}

十一. Hide Method(隐藏函数)

介绍

  1. 场景
    有一个函数,从来没有被其他任何类用到。
  2. 手法
    将这个函数修改为private

动机

  1. 重构往往促使你修改函数的可见度。
  2. 当你面对一个过于丰富、提供了过多行为的接口时,就值得将非必要的取值函数和设值函数隐藏起来。

十二. Replace Constructor with Factory Method(以工厂函数取代构造函数)

介绍

  1. 场景
    你希望在创建对象时不仅仅是做简单的建构工作。
  2. 手法
    将构造函数替换为工厂函数。

范例

重构前

class Employee {
  static ENGINEER = 0;
  static SALESMAN = 1;
  static MANAGER = 2;

  constructor(type) {
    this._type = type
  }
}

重构后

class Employee {
  static ENGINEER = 0;
  static SALESMAN = 1;
  static MANAGER = 2;

  static create(type) {
    return new Employee(type)
  }

  constructor(type) {
    this._type = type
  }
}

十三. Encapsulate Downcast(封装向下转型)

介绍

  1. 场景
    某个函数返回的对象,需要由函数调用者执行向下转型。
  2. 手法
    将向下转型动作移至函数中。

范例

JavaScript无需转型,无法演示该重构手法。

十四. Replace Error Code with Exception(以异常取代错误码)

介绍

  1. 场景
    某个函数返回一个特定的代码,用以表示某种错误情况。
  2. 手法
    改用异常。

范例

重构前

withdraw(amount){
  if(mount > this._balance) {
    return -1
  } else {
    this._balance -= amount
    return 0
  }
}

重构后

withdraw(amount){
  if(mount > this._balance) {
    throw new Error('余额不足')
  } 
  this._balance -= amount
}

十五. Replace Exception with Test(以测试取代异常)

介绍

  1. 场景
    面对一个调用者可以预先检查的条件,你抛出了一个异常。
  2. 手法
    修改调用者,使它在调用函数之前先做检查。

动机

  1. 异常只应该被用于异常的、罕见的行为,也就是那些产生意料之外的错误的行为。而不应该成为条件检查的替代品。

范例

重构前

class ResourcePool {
  _available;
  _allocated;

  getResource() {
    let result;
    try {
      result = this._available.pop()
      this._allocated.push(result)
      return result
    } catch (error) {
      result = new Resource()
      this._allocated.push(result)
      return result      
    }
  }
}

重构后

class ResourcePool {
  _available;
  _allocated;

  getResource() {
    let result;
    if(this._available.isEmpty()) {
      result = new Resource()
    } else {
      result = this._available.pop()
    }
    this._allocated.push(result)
    return result
  }
}
上一篇 下一篇

猜你喜欢

热点阅读