乐趣区

关于javascript:JavaScript重构

一、根底重构

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;
}
退出移动版