当看见一个过长的函数或者一段需要注释才能让人理解用途的代码,可以将这段代码放进一个独立的函数中,并用新函数名称来解释该函数的用途。

动机

  如果每个函数的粒度都很小,那么函数被服用的机会就更大;其次,这会使高层函数读起来就像一系列注释;再次,如果函数都是细粒度,那么函数的覆写也会更容易些。

  一个函数多长才算合适?长度不是问题,关键在于函数名称和函数本体之间的语意距离。如果提炼可以强化代码的清晰度,那就去做,就算函数名称比提炼出来的代码还长也无所谓。

做法

  • 创造一个新函数,根据这个函数的意图来对它命名(以它“做什么”来命名,而不是以它“怎么做”命名),只要新函数的名称能更好的昭示代码意图,就应该提炼它,但如果想不出一个更有意义的名称,就别动。
  • 检查被提炼代码段,是否引用了“作用域仅限于源函数”的变量(包括局部参数和源函数参数)
  • 检查被提炼代码段,看是否有任何局部变量的值被它改变。如果一个临时变量被修改了,看看是否可以将被提炼代码段处理为一个查询,并将结果赋值给相关变量。
  • 将被提炼代码段中需要读取的局部变量,当作参数传给目标函数。
  • 在愿函数中,将被提炼代码段替换为对目标函数的调用。

范例

const orders = {...}
function printOwing () {
  const e = orders.elements()
  let outstanding = 0.0

  // print banner
  console.log('********************************')
  console.log('******** Customer Owes *********')
  console.log('********************************')

  // calculate outstanding
  white (e.hasMoreElements()) {
    const each = e.nextElements()
    outstanding = each.getAmount()
  }

  // print details
  console.log(`name: ${_name}`)
  console.log(`amount: ${outstanding}`)
}

先将打印banner相关代码(被提炼代码无局部变量)先提取出来:

const orders = {...}

function printOwing () {
  const e = orders.elements()
  let outstanding = 0.0

  printBanner()

  // calculate outstanding
  white (e.hasMoreElements()) {
    const each = e.nextElements()
    outstanding = each.getAmount()
  }

  // print details
  console.log(`name: ${_name}`)
  console.log(`amount: ${outstanding}`)
}

function printBanner () {
  console.log('********************************')
  console.log('******** Customer Owes *********')
  console.log('********************************')
}

再将print details这一部分(被提炼代码有局部变量)提炼为一个带参数的函数(必要的话可以用这种手法处理多个局部变量):

const orders = {...}

function printOwing () {
  const e = orders.elements()
  let outstanding = 0.0

  printBanner()

  // calculate outstanding
  white (e.hasMoreElements()) {
    const each = e.nextElements()
    outstanding = each.getAmount()
  }

  printDetails(outstanding)
}

function printBanner () {
  console.log('********************************')
  console.log('******** Customer Owes *********')
  console.log('********************************')
}

function printDetails (outstanding) {
  console.log(`name: ${_name}`)
  console.log(`amount: ${outstanding}`)
}

继续提取calculate outstanding相关代码:

const orders = {...}

function printOwing () {
  printBanner()
  const outstanding = getOutstanding()
  printDetails(outstanding)
}

function printBanner () {
  console.log('********************************')
  console.log('******** Customer Owes *********')
  console.log('********************************')
}

function printDetails (outstanding) {
  console.log(`name: ${_name}`)
  console.log(`amount: ${outstanding}`)
}

function getOutstanding () {
  const e = orders.elements()
  let outstanding = 0.0
  // calculate outstanding
  white (e.hasMoreElements()) {
    const each = e.nextElements()
    outstanding = each.getAmount()
  }
  return outstanding
}

变量e只在被提炼代码中用到,所以可以将它整个搬到新函数中。outstanding变量在被提炼代码内外都被用到,所以必须让提炼出来的新函数返回它。最后还可以将getOutstanding函数里的outstanding改名为result,遵循命名原则:

function getOutstanding () {
  const e = orders.elements()
  let result = 0.0
  // calculate outstanding
  white (e.hasMoreElements()) {
    const each = e.nextElements()
    result = each.getAmount()
  }
  return result
}

如果代码还对变量outstanding做了其他处理,就必须将它的值作为参数传给目标函数:

const orders = {...}

function printOwing () {
  printBanner()
  const outstanding = getOutstanding(10 * 1.2)
  printDetails(outstanding)
}

function printBanner () {
  console.log('********************************')
  console.log('******** Customer Owes *********')
  console.log('********************************')
}

function printDetails (outstanding) {
  console.log(`name: ${_name}`)
  console.log(`amount: ${outstanding}`)
}

function getOutstanding (initialValue) {
  const e = orders.elements()
  let result = initialValue
  // calculate outstanding
  white (e.hasMoreElements()) {
    const each = e.nextElements()
    result = each.getAmount()
  }
  return result
}

声明

以上摘抄自《重构改善既有代码的设计》一书,代码部分由原来的java改写成javascript。目的仅为加深印象和理解,如有侵权请联系。