一、根底重构
1. 提炼函数 (Extract Function)
1.1 应用场景
如果你须要花工夫浏览一段代码能力弄清它到底在干什么,那么就应该将其提炼到一个函数中,并依据它所做的事为其命名。
1.2 应用步骤
- 发明一个新函数,依据这个函数的用意来对它命名
- 将待提炼的代码从源函数复制到新建的指标函数中。
- 仔细检查提炼出的代码,看看其中是否援用了作用域限于源函数、在提炼出的新函数中拜访不到的变量。若是,以参数的模式将它们传递给新函数。
- 所有变量都解决完之后,编译。
- 在源函数中,将被提炼代码段替换为对指标函数的调用。
- 测试。
- 查看其余代码是否有与被提炼的代码段雷同或类似之 处。如果有,思考应用以函数调用取代内联代码,令其调用提炼出的新函数。
1.3 范例
function printOwing(invoice) { let outstanding = 0; // calculate outstanding for (const o of invoice.orders) { outstanding += o.amount; } // record due date const today = Clock.today; invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate( ) + 30); console.log(`name: ${invoice.customer}`); console.log(`amount: ${outstanding}`); console.log(`due: ${invoice.dueDate.toLocaleDateString()}`); }// 重构function printOwing(invoice) { let outstanding = calculateOutstanding(invoice) recordDueDate(invoice) printDetails(invoice, outstanding)}function calculateOutstanding(invoice) { let outstanding = 0; for (const o of invoice.orders) { outstanding += o.amount; } return outstanding;}function recordDueDate(invoice) { const today = Clock.today; invoice.dueDate = new Date(today.getFullYear(), today.getMonth(), today.getDate( ) + 30);}function printDetails(invoice, outstanding) { console.log(`name: ${invoice.customer}`); console.log(`amount: ${outstanding}`); console.log(`due: ${invoice.dueDate.toLocaleDateString()}`); }
2. 内联函数 (Inline Function)
2.1 应用场景
如果代码中有太多间接层,使得零碎中的所有函数都仿佛只是对另一个函数的简略委托,造成我在这些委托动作之间昏头昏脑,那么我通常都会应用内联函数。
2.2 应用步骤
- 查看函数,确定它不具多态性。
- 找出这个函数的所有调用点。
- 将这个函数的所有调用点都替换为函数本体。
- 每次替换之后,执行测试。
- 删除该函数的定义。
2.3 范例
function reportLines(aCustomer) { const lines = []; gatherCustomerData(lines, aCustomer); return lines;}function gatherCustomerData(out, aCustomer) { out.push(["name", aCustomer.name]); out.push(["location", aCustomer.location]); }// 重构function reportLines(aCustomer) { const lines = []; lines.push(["name", aCustomer.name]); lines.push(["location", aCustomer.location]); return lines;}
3. 提炼变量 (Extract Variable)
3.1 应用场景
表达式有可能非常复杂而难以浏览。
3.2 应用步骤
- 确认要提炼的表达式没有副作用。
- 申明一个不可批改的变量,把你想要提炼的表达式复制 一份,以该表达式的后果值给这个变量赋值。
- 用这个新变量取代原来的表达式。
- 测试。
3.3 范例
在一个函数中,将它们提炼成变量
function price(order) { return order.quantity * order.itemPrice - Math.max(0, order.quantity - 500) * order.itemPrice * 0.05 + Math.min(order.quantity * order.itemPrice * 0.1, 100);}// 重构function price(order) { const basePrice = order.quantity * order.itemPrice; const quantityDiscount = Math.max(0, order.quantity - 500) * order.itemPrice * 0 .05; const shipping = Math.min(basePrice * 0.1, 100); return basePrice - quantityDiscount + shipping;}
在一个类中,将它们提炼成办法
class Order { constructor(aRecord) { this._data = aRecord; } get quantity() { return this._data.quantity; } get itemPrice() { return this._data.itemPrice; } get price() { return this.quantity * this.itemPrice - Math.max(0, this.quantity - 500) * this.itemPrice * 0.05 + Math.min(this.quantity * this.itemPrice * 0.1, 100); }}// 重构class Order { constructor(aRecord) { this._data = aRecord; } get quantity() { return this._data.quantity; } get itemPrice() { return this._data.itemPrice; } get price() { return this.basePrice - this.quantityDiscount + this.shipping; } get basePrice() { return this.quantity * this.itemPrice; } get quantityDiscount() { return Math.max(0, this.quantity - 500) * this.itemPrice * 0.05; } get shipping() { return Math.min(this.basePrice * 0.1, 100); } }
4. 内联变量 (Inline Variable)
4.1 应用场景
有些时候,变量并不比表达式自身更具表现力,可能会障碍重构左近的代码。若果真如此,就应该通过内联的手法打消变量。
4.2 应用步骤
- 查看确认变量赋值语句的右侧表达式没有副作用。
- 如果变量没有被申明为不可批改,先将其变为不可批改,并执行测试。
- 找到第一处应用该变量的中央,将其替换为间接应用赋值语句的右侧表达式。
- 测试。
- 反复后面两步,逐个替换其余所有应用该变量的中央。
- 删除该变量的申明点和赋值语句。
- 测试。
4.3 范例
let basePrice = anOrder.basePrice;return (basePrice > 1000);// 重构return anOrder.basePrice > 1000;
5. 扭转函数申明 (Change Function Declaration)
5.1 应用场景
如果我一个函数、函数的参数的名字不能一眼看出它的用处,一旦发现了就得尽快给它改名。
5.2 应用步骤
- 先写一句正文形容这个函数的用处。
- 再把这句正文变成函数的名字。
- 先实现函数改名。
- 测试。
- 而后增加参数。
- 测试。
5.3 范例
function inNewEngland(aCustomer) { return ["MA", "CT", "ME", "VT", "NH", "RI"].includes(aCustomer.address.state);} const newEnglanders = someCustomers.filter(c => inNewEngland(c));// 重构function inNewEngland(stateCode) { return ["MA", "CT", "ME", "VT", "NH", "RI"].includes(stateCode);}const newEnglanders = someCustomers.filter(c => inNewEngland(c.address.state));
6. 封装变量 (Encapsulate Variable)
6.1 应用场景
对于所有可变的数据,只有它的作用域超出单个函数,就将其封装起来,只容许通过函数拜访。
6.2 应用步骤
- 创立封装函数,在其中拜访和更新变量值。
- 执行动态查看。
- 逐个批改应用该变量的代码,将其改为调用适合的封装函数。每次替换之后,执行测试。
- 限度变量的可见性。
- 测试。
- 如果变量的值是一个记录,思考应用封装记录
6.3 范例
7. 变量改名 (Rename Variable)
7.1 应用场景
变量要能够很好地解释一段程序在干什么,对于作用域超出一次函数调用的字段,则须要更用心命名。
7.2 应用步骤
- 如果变量被宽泛应用,思考使用封装变量将其封装起来。
- 找出所有应用该变量的代码,逐个批改。
- 测试。
7.3 范例
8. 引入参数对象 (Introduce Parameter Object)
8.1 应用场景
变量要能够很好地解释一段程序在干什么,对于作用域超出一次函数调用的字段,则须要更用心命名。
8.2 应用步骤
- 如果临时还没有一个适合的数据结构,就创立一个。
- 测试。
- 应用扭转函数申明给原来的函数新增一个参数, 类型是新建的数据结构。
- 测试。
- 调整所有调用者,传入新数据结构的适当实例。每批改 一处,执行测试。
- 用新数据结构中的每项元素,逐个取代参数列表中与之 对应的参数项,而后删除原来的参数。
- 测试。
8.3 范例
const station = { name: "ZB1", readings: [ {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 readingsOutsideRange(station, min, max) { return station.readings.filter(r => r.temp < min || r.temp > max);}const alerts = readingsOutsideRange(station, operatingPlan.temperatureFloor, operatingPlan.temperatureCeiling);// 重构function readingsOutsideRange(station, range) { return station.readings.filter(r => !range.contains(r.temp));}function contains(arg) { return (arg >= this.min && arg <= this.max);}const alerts = readingsOutsideRange(station, range);
9. 函数组合成类 (Combine Functions into Class)
9.1 应用场景
如果发现一组函数如影随行地操作同一块数据(通常是将这块数据作为参数传递给函数),就须要组建一个类了。
9.2 应用步骤
- 使用封装记录对多个函数共用的数据记录加以封装。
- 对于应用该记录构造的每个函数,使用搬移函数将其移入新类。
- 用以解决该数据记录的逻辑能够用提炼函数提炼进去,并移入新类。
9.3 范例
const reading = {customer: "ivan", quantity: 10, month: 5, year: 2017};const aReading = acquireReading();const baseCharge = baseRate(aReading.month, aReading.year) * aReading.quantity;const base = (baseRate(aReading.month, aReading.year) * aReading.quantity);const taxableCharge = Math.max(0, base - taxThreshold(aReading.year));const basicChargeAmount = calculateBaseCharge(aReading);function calculateBaseCharge(aReading) { return baseRate(aReading.month, aReading.year) * aReading.quantity;}// 重构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 calculateBaseCharge() { return baseRate(this.month, this.year) * this.quantity; } get baseCharge() { return baseRate(this.month, this.year) * this.quantity; } get taxableCharge() { return Math.max(0, this.baseCharge - taxThreshold(this.year)); }}
10. 函数组合成变换 (Combine Functions into Transform)
10.1 应用场景
如果代码中会对源数据做更新,那么应用类要好得多; 如果应用变换,派生数据会被存储在新生成的记录中,一旦源数据被批改,我就会遭逢数据不统一。
10.2 应用步骤
- 创立一个变换函数,输出参数是须要变换的记录,并间接返回该记录的值。
- 筛选一块逻辑,将其主体移入变换函数中,把后果作为字段增加到输入记录中。批改客户端代码,令其应用这个新字段。
- 测试。
- 针对其余相干的计算逻辑,反复上述步骤。
10.3 范例
const reading = {customer: "ivan", quantity: 10, month: 5, year: 2017};const aReading = acquireReading();const baseCharge = baseRate(aReading.month, aReading.year) * aReading.quantity;const base = (baseRate(aReading.month, aReading.year) * aReading.quantity);const taxableCharge = Math.max(0, base - taxThreshold(aReading.year));const basicChargeAmount = calculateBaseCharge(aReading);function calculateBaseCharge(aReading) { return baseRate(aReading.month, aReading.year) * aReading.quantity;}// 重构function enrichReading(original) { const result = _.cloneDeep(original); result.baseCharge = calculateBaseCharge(result); result.taxableCharge = Math.max(0, result.baseCharge - taxThreshold(result.year)); return result;}const rawReading = acquireReading();const aReading = enrichReading(rawReading);const base = aReading.baseCharge;const taxableCharge = aReading.taxableCharge;
11. 拆分阶段 (Split Phase)
11.1 应用场景
一段代码在同时解决两件不同的事,就要把它拆分成各自独立的模块。可能你有一段解决逻辑,其输出数据的格局不合乎计算逻辑的要求,所以你得先对输出数据做一番调整,使其便于解决。也可能是你把数据处理逻辑分成程序执行的多个步骤,每个步骤负责的工作全然不同。
11.2 应用步骤
- 将第二阶段的代码提炼成独立的函数。
- 测试。
- 引入一个直达数据结构,将其作为参数增加到提炼出的新函数的参数列表中。
- 测试。
- 逐个查看提炼出的“第二阶段函数”的每个参数。如果某个参数被第一阶段用到,就将其移入直达数据结构。每次搬移之后都要执行测试。
- 对第一阶段的代码使用提炼函数,让提炼出的函 数返回直达数据结构。
11.3 范例
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;}// 重构function priceOrder(product, quantity, shippingMethod) { const priceData = calculatePricingData(product, quantity); return applyShipping(priceData, shippingMethod);}function calculatePricingData(product, quantity) { const basePrice = product.basePrice * quantity; const discount = Math.max(quantity - product.discountThreshold, 0) * product.basePrice * product.discountRate; return { basePrice: basePrice, quantity: quantity, discount:discount }}; function applyShipping(priceData, shippingMethod) { const shippingPerCase = (priceData.basePrice > shippingMethod.discountThreshold) ? shippingMethod.discountedFee : shippingMethod.feePerCase; const shippingCost = priceData.quantity * shippingPerCase; return priceData.basePrice - priceData.discount + shippingCost;}