过长的函数

78 阅读3分钟

在软件开发时,为了快速开发我们可能会在一个函数中写多个逻辑,导致函数特别的臃肿,可读性变差,同时也增加了后期的维护成本。

此时我们就需要给函数减肥了。我们应该更加积极地分解函数。我们遵循这样的一条原则:每当我们需要以注释来说明点什么的时候,我们就应该把需要注释的部分写进一个独立的函数中,并以其用途命名。重点在于区分函数“做什么”和“如何做”。

最常见的分解手段就是提炼函数。通过提炼函数得到一个新的独立函数。如果这个新函数有太多的参数时,可以运用以查询取代临时变量消除。引入参数对象保持对象完整将过长的参数列表变得简洁一些。或者使用以命令取代函数

以查询取代临时变量

这项重构手法在类中施展效果最好,因为在类中为待提炼函数提供了同一个上下文。如果不在类中,就需要为顶层函数添加过多的参数,这将冲淡提炼函数的好处。

class Order{
  constructor(quantity, item) { 
  this._quantity = quantity; 
  this._item = item; 
  }
  
  get price() {
  var basePrice = this._quantity * this._item.price; 
  return basePrice * 10; 
  }
}

改造

// 将basePrice声明成常量,同时将右侧的赋值逻辑提炼成函数
 get price() {
  const basePrice = this.basePrice
  return basePrice * 10; 
}

basePrice(){
 return this._quantity * this._item.price
}

引入参数对象

在使用提炼函数等进行重构时,被提炼的部分可能会使用许多外部的变量,我们一般会通过参数的方式将变量传入,但当参数过多时就会导致代码的可读性变差。此时我们可以通过创建参数对象减少参数数量

做法

  1. 创建一个新的数据结构(倾向于使用类,因为引入参数对象往往是开始重构的第一步,在后续的过程中可能还需要对其进行操作,使用类的话就会很方便)
  2. 给原来的函数增加一个参数,参数类型就是新创建的数据结构
  3. 调整所有调用者,传入新数据结构的参数
  4. 用新数据结构中的元素取代原来的参数列表
function readingsOutsideRange(station, min, max) { 
  return station.readings
    .filter(r => r.temp < min || r.temp > max);
}

对于min、max参数我们就可以使用参数对象进行重构

// 创建一个类
Class NumberRange{
  constructor(min, max) {
  this._data = { min: min, max: max };
  } 
  get min() {
  return this._data.min;
  } 
  get max() {
  return this._data.max; 
  }
}

const range = new NumberRange(0,10)

function readingsOutsideRange(station,range) {
  return station.readings
    .filter(r => r.temp < range.min || r.temp > range.max);
}

保持对象完整

当看到从一个对象中导出几个参数后,又将这几个参数一起传入到另一个函数中。我们就可以考虑直接将整个对象都传递给这个函数。当我们之后想从对象中在传递其它参数时就不必更改函数的参数列表,通过也可以减少参数列表的长度

const width = room.width
const height = room.width

function roomArea(width,height)

// 改为将整个room传入
function roomArea(room)

以命令取代函数

function score(candidate, medicalExam, scoringGuide) {
  let result = 0; let healthLevel = 0;
  // long body code
}

class Scorer { 
constructor(candidate, medicalExam, scoringGuide) {
this._candidate = candidate;
this._medicalExam = medicalExam; 
this._scoringGuide = scoringGuide; 
  } 
execute() { 
this._result = 0; 
this._healthLevel = 0; 
// long body code 
  } 
}

命令对象提供了更大的控制灵活性和更强的表达能力。除了函数调用本身,命令对象还可以支持附加的操作,例如撤销操作。我可以通过命令对象提供的方法来设置命令的参数值,从而支持更丰富的生命周期管理能力

做法

  1. 为要包装的函数,创建一个同名的空类
  2. 将函数体移动到类中
  3. 保持原来的函数作为转发函数
  4. 考虑将原来的参数,通过构造函数传入