【重构】四、第一组重构

相对较为频繁可用到的重构方法

1、提炼函数

将意图和实现分开。如果你需要写上一大段注释去说明代码用途时,说明改代码需要被提炼到独立函数,并且注释便是一个函数的好名字

// bad
function printOwing(invoice){
    let outstanding = 0;

    console.log("*******************")
    console.log("***Customer Owes***")
    console.log("*******************")

    // calculate outstanding
    for(const of of invoice.orders){
            outstanding += o.amount
    }

    // record due date
    const today = Clock.today
    invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30)

    // print details
    console.log(`name: ${invoice.customer}`)
    console.log(`amount: ${outstanding}`)
    console.log(`due: ${invoice.dueDate.toLocaleDateString()}`)

}

// good
// 根据注释提取独立函数
function printOwing(invoice){
    printBanner()
    calculateOutstanding(invoice)
    recordDueDate(invoice)
    printDetails(invoice, outstanding)
}

function printBanner() {
    console.log("*******************")
    console.log("***Customer Owes***")
    console.log("*******************")
}
function printDetails(invoice, outstanding) {
    // print details
    console.log(`name: ${invoice.customer}`)
    console.log(`amount: ${outstanding}`)
    console.log(`due: ${invoice.dueDate.toLocaleDateString()}`)
}
fucntion recordDueDate(invoice) {
            const today = Clock.today
    invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 30)
}

function calculateOutstanding(invoice) {
    let result = 0;
    for(const of of invoice.orders){
            result += o.amount
    }
    return result
}

短函数的大量调用会影响性能的问题,在如今已经非常罕见

短函数常常能让编译器的优化功能运转更良好,因为短函数可以更容易地被缓存

2、内联函数

如果函数内部代码和名称同样清晰易读,可直接使用其代码,没必要加多一层嵌套

function getRating(driver){
    return moreThanFiveLateDeliveries(driver) ? 2: 1
}

function moreThanFiveLateDeliveries(driver) {
    return driver.numberOfLateDeliveries > 5
}
function getRating(driver){
    return  driver.numberOfLateDeliveries > 5 ? 2: 1
}

3、提炼变量

局部变量可以帮助我们将表达式分解为比较容易管理的形式

// bad
{
    return order.quantity * order.itemPrice - Math.max(0, order.quantity - 500) * order.itemPrice * 0.05 + Math.min(order.quantity * order.itemPrice * 0.1, 100)
}

// good
{
    const basePrice = order.quantity * order.itemPrice;
    const quantityDiscount = Math.max(0, order.quantity - 500) * order.itemPrice * 0.05
    const shipping = Math.min(order.quantity * order.itemPrice * 0.1, 100)

    return basePrice - quantityDiscount + shipping
}

4、内联变量

变量能给表达式提供有意义的名字,但是有的时候,名字并不比表达式本身更具有表现力

// bad
let basePrice = anOrder.basePrice;
return basePrice > 200

// good
return anOrder.basePricce > 200

5、改变函数声明

  • 简单做法,直接更改函数名字
// bad 
function circum(radius) { return 2 * Math.PI * radius }

// good
function circummFerence(radius){ return 2 * Math.PI * radius }
  • 迁移式做法,建立中间函数,再一步步替换,可以减少重构带来的危险
// bad 
function circum(radius) { return 2 * Math.PI * radius }

// good 
function circum(radius) { return circummFerence() }
function circummFerence(radius){ return 2 * Math.PI * radius }

6、封装变量

对于所有可变的数据,只要它的作用于超出单个函数,可以考虑封装起来,只允许通过函数访问

// bad
let defaultOwner = { first:'Martin', lastName: 'Fowler' }
spaceShip.owner = defaultOwner
defaultOwner = { first:'Rebecca', lastName: 'Lee' }

// good 
// defaultOwner.js
let defaultOwner = { first:'Martin', lastName: 'Fowler' } 
export function getDefaultOwner() { return defaultOwner }
export function setDefaultOwner(arg) { defaultOwner = arg }
spaceShip.owner = getDefaultOwner()
setDefaultOwner({first:'Rebecca', lastName: 'Lee'})

// 如果需要阻止对数据的更改,可以考虑通过封装成类来实现

class Person {
    constrcutor(data) {
            this._lastName = data.lastName;
            this._firstName = data.firstName;
    }
    get lastName() { return this._lastName }
    get firstName() { return this._firstName }
}
// 由于没有设置set 方法,外部只能获取lastName 和 firstName,无法对进行修改

7、变量改名

如果需要改名的变量只作用于一个函数(临时变量或者参数),可对其直接修改

// bad
let a = height * width

// good
let area = height * width

如果变量的作用于不止于单个函数,比如有些地方是读取变量,有些地方是设置变量,则建议使用第6点 “封装变量”方法

// bad

// 定义
let tpHd = 'untitled'
// 读取
return += `<h1>${tpHd}</h1>`
// 赋值
tpHd = obj['articleTitle']


// good

// 读取
return += `<h1>${title()}</h1>`
//赋值
setTitle(obj['articleTitle'])

// 定义
let _title = 'untitled'
function title() { return _title }
function setTitile(arg) { _title = arg}

8、引入参数对象

如果一组数据项总是结伴而行,出没于一个又一个函数,可以代之以一个数据结构

// bad
function amountInvoiced(startDate, endDate) {}
function amountReviced(startDate, endDate) {}
function amountOverdue(startDate, endDate) {}

// good
function amountInvoiced(aDateRange) {}
function amountReviced(aDateRange) {}
function amountOverdue(aDateRange) {}

下面要展示的代码会查看一组温度读数,检查是否有任何一条读数超出了指定的运作温度范围


// bad
const station = {
  name: 'ZB1',
  reading: [
    { temp: 47, time: '2016-11-10 09:10' },
    { temp: 53, time: '2016-11-10 09:20' },
    { temp: 58, time: '2016-11-10 09:30' },
    { temp: 53, time: '2016-11-10 09:40' },
    { temp: 51, time: '2016-11-10 09:50' },
  ],
};
// 负责找到超出指定范围的温度读数
function readingOutsideRange(station, max, min) {
  return station.readins.filter((r) => r.temp < min || r.temp > max);
}
// 调用
alerts = readingOutsideRange(
  station,
  operatingPlan.temperatureFloor,
  operatingPlan.temperatureCeiling
);

// good
class NumberRange {
  constructor(min, max) {
    this._data = { min, max };
  }
  get min() {
    return this._data.min;
  }
  get max() {
    return this._data.max;
  }
  contains(arg) {
    return arg >= this.min && arg <= this.max;
  }
}

function readingOutsideRange(station, range) {
  return station.readins.filter((r) => !range.contains(r.temp));
}
alerts = readingOutsideRange(station, range);

9、函数组合成类

如果发现一组函数影形不离地操作同一块数据(通常是将这块数据座位参数传递给函数),就可以考虑组件一个类了。

类能明确地给这些函数提供一个公用的环境,在对象内部调用这些函数可以少传许多参数,从而简化函数调用

const reading = {
  customer: 'ivan',
  quantity: 10,
  month: 5,
  year: 2017,
};
// bad
// 客户端1
const baseCharge = baseRate(reading.month, reading.year) * reading.quantity;

// 客户端2
const base = baseRate(reading.month, reading.year) * reading.quantity;
const taxableCharge = Math.max(0, base - taxThreehold(reading.year));

// 客户端3
const baseChargeAmount = calculateBaseCharge(reading);
function calculateBaseCharge(reading) {
  return baseRate(reading.month, reading.year) * reading.quantity;
}
const reading = {
  customer: 'ivan',
  quantity: 10,
  month: 5,
  year: 2017,
};

// 计算基础费用的公示被用了两遍,而客户端3的调用虽然封装了一层,但是从结构上看不出来数据的关联性,
// 如果把关联数据和方法封装成类,则结构会更清晰明了
// good
class Reading {
  constructor(data) {
    this._customer = data.customer;
    this._quantity = data.quantity;
    this._month = data.month;
    this._year = data.year;
  }
  get customer() {
    return this._customer;
  }
  get quantity() {
    return this._quantity;
  }
  get month() {
    return this._month;
  }
  get year() {
    return this._year;
  }
  get baseCharge() {
    return baseRate(this.month, this.year) * this.quantity;
  }
  get taxableCharge() {
    return Math.max(0, this.baseCharge - taxThreehold(this.year));
  }
}

// 客户端1
const baseCharge = new Reading(reading).baseCharge;

// 客户端2
const base = new Reading(reading).baseCharge;
const taxableCharge = new Reading(reading).taxableCharge;

// 客户端3
const baseChargeAmount = new Reading(reading).baseCharge;

10、函数组合成变换

在软件中,经常需要把数据 “喂” 给一个程序,让它可以计算出各种派生信息。而这些派生数据可能会在几个不同的地方用到,因此有些计算逻辑也会在用到派生数据的地方重复用到。

通过做法可以考虑使用“数据变换函数”:这种函数接受元数据作为输入,计算出所有的派生数据,将派生数据以字段形式填入输出数据。

函数组成变换的替代方案是“函数组合成类”,两者的区别在于,如果代码中会对源数据做更新,使用类会好得多;如果使用变换,派生数据会被存储在新生成的记录中,一旦源数据被修改,就会遭遇数据不一致。

const reading = {
	customer: 'ivan',
	quantity: 10,
	month: 5,
	year: 2017
}

// bad
// 客户端1
const baseCharge = baseRate(reading.month, reading.year) * reading.quantity

// 客户端2
const base = baseRate(reading.month, reading.year) * reading.quantity
const taxableCharge = Math.max(0, base - taxThreehold(reading.year))

// 客户端3
const baseChargeAmount = calculateBaseCharge(reading)
function calculateBaseCharge(reading) {
	return baseRate(reading.month, reading.year) * reading.quantity
}

// good
// 通过enrichReading 把数据和方法集中在一块处理
function enrichReading(aReading) {
	let result = _deepClone(aReading)
	result.baseCharge = calculateBaseCharge(aReading)
	result.taxableCharge = Math.max(0, aReading.baseCharge - taxThreehold(reading.year))
	return result
}
// 客户端1
const baseCharge = enrichReading(reading).baseCharge
// 客户端2
const base = enrichReading(reading).baseCharge
const taxableCharge = enrichReading(reading).taxableCharge
// 客户端3
const baseChargeAmount = enrichReading(reading).baseCharge

11、拆分阶段

当一段代码在同时处理两件不同的事,就应该把它拆分成各自独立的模块,这样可以单独处理每个主题。

// bad
function priceOrder(product, quantity, shippingMethod) {
  // 根据商品信息,计算商品相关的价格
  const basePrice = product.basePrice * quantity;
  const discount =
    Math.max(quantity - product.discountThreshold, 0) *
    product.basePrice *
    product.discountRate;
  // 根据配送信息,计算配算成本
  const shippingPerCase =
    basePrice > shippingMethod.discountThreshold
      ? shippingMethod.discountedFee
      : shippingMethod.feePerCase;
  const shippingCost = quantity * shippingPerCase;
  const price = basePrice - discount + shippingCost;
  return price;
}

// good
function priceOrder(product, quantity, shippingMethod) {
  const priceData = calculatePriceData(product, quantity);
  return applyShipping(priceData);
}
function calculatePriceData(product, quantity) {
  const basePrice = product.basePrice * quantity;
  const discount =
    Math.max(quantity - product.discountThreshold, 0) *
    product.basePrice *
    product.discountRate;
  return { basePrice, quantity, discount };
}
function applyShipping(priceData, shippingMethod) {
  const shippingPerCase =
    priceData.basePrice > shippingMethod.discountThreshold
      ? shippingMethod.discountedFee
      : shippingMethod.feePerCase;
  const shippingCost = priceData.quantity * shippingPerCase;
  const price = priceData.basePrice - priceData.discount + shippingCost;
}
  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值