一. Extract Method(提炼函数)
介绍
- 场景
你有一段代码可以被组织在一起并独立出来。 - 手法
将这段代码放进一个独立的函数中,并让函数名称解释该函数的作用。
动机
- 一段过长的函数或者需要注释才能让人理解用途的函数可以放进一个独立的函数中。
- 使用简短且命名良好的函数的好处
① 细粒度函数易于复用。
② 细粒度函数易于复写。
③ 高层函数读起来像一系列注释。 - 函数名好坏的关键在于函数名称与函数本体之间的语义距离,而不是函数名称的长短。
做法
① 创造新函数,以函数的意图命名。
② 将提炼出的代码从源函数复制到新建的目标函数中。
③ 检查提炼出的代码是否是否有源函数局部变量和参数。
④ 检查提炼出的代码是否有仅用于被提炼代码段的临时变量。
⑤ 检查提炼出的代码是否有源函数的任何局部变量的值被它改变。
范例
重构前
printOwing() {
const arr = [1,2,3]
let sum = 0
//print banner
console.log(`--------------`)
console.log(`Customer Owes`)
console.log(`--------------`)
arr.forEach(item => {
sum += item
})
//print details
console.log(`name: ${this._name}`)
console.log(`amount: ${sum}`)
}
重构后
printOwing() {
const sum = this.getOutStanding()
this.printBanner()
this.printDetails(sum)
}
getOutStanding() {
const arr = [1,2,3]
let sum = 0
arr.forEach(item => {
sum += item
})
return sum
}
printBanner() {
console.log(`--------------`)
console.log(`Customer Owes`)
console.log(`--------------`)
}
printDetails(sum) {
console.log(`name: ${this._name}`)
console.log(`amount: ${sum}`)
}
二. Inline Method(内联函数)
介绍
- 场景
一个函数的本体与名称同样清楚易懂。 - 手法
在函数调用点插入函数本体,然后移除该函数。
范例
重构前
class InlineMethod{
constructor() {
this._numberOfLateDeliveries = 6
}
getRatine() {
return this.moreThanFiveLateDeliveries() ? 2 : 1
}
moreThanFiveLateDeliveries() {
return this._numberOfLateDeliveries > 5
}
}
重构后
class InlineMethod{
constructor() {
this._numberOfLateDeliveries = 6
}
getRatine() {
return this._numberOfLateDeliveries > 5 ? 2 : 1
}
}
三. Inline Temp(内联临时变量)
介绍
- 场景
你有一个临时变量,只被一个简单表达式赋值一次,而它妨碍了其他重构手法。 - 手法
将所有对该变量的引用动作替换为对它赋值的那个表达式本身。
范例
重构前
const basePrice = this.anOrder.basePrice();
return basePrice > 1000
重构后
return this.anOrder.basePrice() > 1000
四. Replace Temp with Query(以查询取代临时变量)
介绍
- 场景
你的程序以一个临时变量保存某一表达式的运算结果。 - 手法
将这个表达式提炼到一个独立函数中,将这个临时变量的所有引用点替换为对新函数的调用。此后,新函数就可以被其他函数使用。
动机
- 临时变量只能在所属函数中使用,查询方法可以被用一个类中所有方法调用。
- 局部变量会使代码难以被提炼,所以应该尽可能把他们替换成查询方法。
范例
重构前
class ReplaceTempWithQuery {
getPrice() {
const basePrice = this._quantity * this._itemPrice
let discountFactory
if(basePrice > 1000) {
discountFactor = 0.95
} else {
discountFactor = 0.98
}
return basePrice * discountFactor
}
}
重构后
class ReplaceTempWithQuery {
getPrice() {
return basePrice() * discountFactor()
}
basePrice() {
return this._quantity * this._itemPrice
}
discountFactor() {
return this.basePrice() > 1000 ? 0.95 : 0.98
}
}
五. Introduce Explaining Variable(引入解释性变量)
介绍
- 场景
你有一个复杂的表达式。 - 手法
将该复杂表达式(或其中一部分)的结果放进一个临时变量,以此变量名称来解释表达式用途。
动机
- 临时变量可以帮助你将复杂且难以理解的表达式分解为容易管理的形式。
- 我不用常使用该重构手法,我比较喜欢使用 6.1 Extract Method(提炼函数),因为同一个对象中的任何部分,都可以根据自己的需要取用这些提炼出来的函数。
范例
重构前
price() {
//price is base price - quantity discount + shipping
return this._quantity * this._itemPrice -
Math.max(0, this._quantity - 500) * this._itemPrice * 0.05 +
Math.min(this._quantity * this._itemPrice * 0.1, 100.0)
}
重构后:Introduce Explaining Variable(引入解释性变量)
price() {
const basePrice = this._quantity * this._itemPrice
const quantityDiscount = Math.max(0, this._quantity - 500) * this._itemPrice * 0.05
const shipping = Math.min(basePrice * 0.1, 100.0)
return basePrice - quantityDiscount + shipping
}
重构后:Extract Method(提炼函数)
price() {
return this.basePrice() - this.quantityDiscount() +this.shipping()
}
basePrice() {
return this._quantity * this._itemPrice
}
quantityDiscount(){
return Math.max(0, this._quantity - 500) * this._itemPrice * 0.05
}
shipping() {
Math.min(basePrice * 0.1, 100.0)
}
六. Split Temporary Varibale(分解临时变量)
介绍
- 场景
你的程序有某个临时变量被赋值超过一次,它既不是循环变量,也不被用于收集计算结果。 - 手法
针对每次赋值,创造一个独立、对应的临时变量。
动机
- 临时变量有各种不同用途,其中某些用途会很自然地导致多次赋值。“循环变量”和“结果收集变量”就是两个典型例子。
- 除了这两种情况,如果临时变量承担多个责任,它就应该被替换(分解)为多个临时变量,每个变量只被赋值一次,只承担一个责任。
范例
重构前
let temp = 2 * (_width + _height)
console.log(temp)
temp = _height * _width
console.log(temp)
重构后
const perimeter = 2 * (_width + _height)
console.log(perimeter)
const area = _height * _width
console.log(area)
七. Remove Assignments to Parameters(移除对参数的赋值)
介绍
- 场景
代码对一个参数进行赋值。 - 手法
以一个临时变量取代该参数的位置。
动机
- 对象的引用是按值传递的。因此,我们可以修改参数对象的内部状态,但对参数对象重新赋值是没有意义的。
- 在按值传递的情况下,对参数的重新赋值会降低代码的清晰度,而且混用了按值传递和按引用传递这两种参数传递方式。
范例
重构前
const discount = (inputVal, quantity, yearToDate) => {
if(inputVal > 50) inputVal -= 2
if(quantity > 100) inputVal -= 1
if(yearToDate > 1000) inputVal -= 4
return inputVal
}
重构后
const discount = (inputVal, quantity, yearToDate) => {
let result = inputVal
if(inputVal > 50) result -= 2
if(quantity > 100) result -= 1
if(yearToDate > 1000) result -= 4
return result
}
八. Replace Method with Method Object(以函数对象取代函数)
介绍
- 场景
你有一个大型函数,其中对局部变量的使用使你无法采用Extract Method(提炼函数)。 - 手法
将这个函数放进一个单独对象中,如此一来局部变量就成了对象内的字段。然后你可以在同一个对象中将这个大型函数分解为多个小型函数。
动机
- 将相对独立的代码从大型函数中提炼出来,可以大大提高代码的可读性。但是,局部变量的存在会增加函数分解难度。
- 该重构手法会将所有局部变量都变成函数对象的字段。然后你就可以对象这个新对象使用Extract Method(提炼函数)创造出新函数,从而将原本的大型函数拆解。
范例
重构前
class Account {
gamma(inputVal, quantity, yearToDate) {
let importantValue1 = (inputVal * quantity) + this.delta()
let importantValue2 = (inputVal * yearToDate) + 100
if(yearToDate - importantValue1 > 100) {
importantValue2 -= 20
}
let importantValue3 = importantValue2 * 7
return importantValue3 - 2 * importantValue1
}
}
重构后
class Account {
gamma(inputVal, quantity, yearToDate) {
return new Gamma(this, inputVal, quantity, yearToDate).compute()
}
}
class Gamma {
_account;
inputVal;
quantity;
yearToDate;
importantValue1;
importantValue2;
importantValue3;
constructor(account, inputVal, quantity, yearToDate) {
this._account = account
this.inputVal = inputVal
this.quantity = quantity
this.yearToDate = yearToDate
}
compute() {
this.importantValue1 = (this.inputVal * this.quantity) + this._account.delta()
this.importantValue2 = (this.inputVal * this.yearToDate) + 100
if(this.yearToDate - this.importantValue1 > 100) {
this.importantValue2 -= 20
}
this.importantValue3 = importantValue2 * 7
return this.importantValue3 - 2 * this.importantValue1
}
}
现在可以轻松地对compute()
函数采取6.1 Extract Method(提炼函数),不必担心参数传递的问题。
九. Substitute Algorithm(替换算法)
介绍
- 场景
你想要把某个算法替换为另一个更清晰的算法。 - 手法
将函数本体替换为另一个算法。
动机
- 如果你发现做一件事情可以有更清晰的方式,就应该以较清晰的方式取代复杂的方式。
- 替换一个巨大而复杂的算法是非常困难的,只要先将它分解为教简单的小型函数,然后你才能很有把握地进行算法替换工作。
范例
重构前
const foundPerson = (people) => {
for(let i = 0; i < people.length; i++) {
if(people[i] === 'Don') {
return 'Don'
}
if(people[i] === 'John') {
return 'John'
}
if(people[i] === 'Kent') {
return 'Kent'
}
return ''
}
}
重构后
const foundPerson = (people) => {
let candidates = ['Don', 'John', 'Kent']
for(let i = 0; i < people.length; i++) {
if(candidates.includes(people[i])) {
return people[i]
}
}
return ''
}
网友评论