转载自原作者:俺是谁
https://juejin.cn/post/7131211363493347335
(有删改)
我们先引入一句话:
代码主要是为了写给人看的,而不是写给机器看的,只是顺便也能用机器执行而已。
代码和语言文字一样是为了表达思想、记载信息,所以写得清楚能更有效地表达。本文多数总结自《重构:改善既有代码的设计(第2版)》我们直接进入正题,上代码!
【方法一】提炼函数
一句话——将一段代码提炼到一个独立的函数中,并以这段代码的作用命名。
用途
如果需要花时间浏览一段代码才能弄清楚它到底要干什么,那么这时候就应该将其提炼到一个函数中,并根据它所做的事命名。以后再读这段代码时,一眼就能知道这个函数的用途。
// ==================重构前==================
function printOwing(invoice) {
let outstanding = 0;
console.log("***********************");
console.log("**** Customer Owes ****");
console.log("***********************");
}
// ==================重构后==================
function printOwing(invoice) {
let outstanding = 0;
printBanner()
}
function printBanner() {
console.log("***********************");
console.log("**** Customer Owes ****");
console.log("***********************");
}
【方法二】函数参数化
一句话——以参数的形式传入不同的值,消除重复函数。
用途
如果发现两个函数逻辑非常相似, 只有一些字面量值不同, 可以将其合并成一个函数, 以参数的形式传入不同的值, 从而消除重复。
// ==================重构前==================
// 点击异常项
clickFaultsItem(item){
this.$u.route({
url:'xxx',
params:{
id: item.id,
type: '异常'
}
})
}
// 点击正常项
clickNormalItem(item){
this.$u.route({
url:'xxx',
params:{
id: item.id,
type: '正常'
}
})
}
// ==================重构后==================
clickItem(id, type){
this.$u.route({
url:'xxx',
params:{id, type}
})
}
【方法三】提炼变量
一句话——提炼局部变量替换表达式。
用途
一个表达式有可能非常复杂且难以阅读。这种情况下, 可以提炼出一个局部变量帮助我们将表达式分解为比较容易管理的形式 ,这样的变量在调试时也很方便。
// ==================重构前==================
function price(order) {
//价格 = 商品原价 - 数量满减价 + 运费
return order.quantity * order.price -
Math.max(0, order.quantity - 500) * order.price * 0.05 +
Math.min(order.quantity * order.price * 0.1, 100);
}
// ==================重构后==================
function price(order) {
const basePrice = order.quantity * order.price;
const quantityDiscount = Math.max(0, order.quantity - 500) * order.price * 0.05;
const shipping = Math.min(basePrice * 0.1, 100);
return basePrice - quantityDiscount + shipping;
}
【方法四】内联变量
一句话——用变量右侧表达式消除变量,这是提炼变量的逆操作。
用途
当变量名字并不比表达式本身更具表现力时可以采取该方法。
// ==================重构前==================
let basePrice = anOrder.basePrice;
return (basePrice > 1000);
// ==================重构后==================
return anOrder.basePrice > 1000
【方法五】封装变量
一句话——将变量封装起来,只允许通过函数访问。
用途
对于所有可变的数据, 只要它的作用域超出单个函数,就可以采用封装变量的方法。数据被使用得越广, 就越是值得花精力给它一个体面的封装。
// ==================重构前==================
let defaultOwner = {firstName: "Martin", lastName: "Fowler"};
// 访问
spaceship.owner = defaultOwner;
// 赋值
defaultOwner = {firstName: "Rebecca", lastName: "Parsons"};
// ==================重构后==================
function getDefaultOwner() {return defaultOwner;}
function setDefaultOwner(arg) {defaultOwner = arg;}
// 访问
spaceship.owner = getDefaultOwner();
// 赋值
setDefaultOwner({firstName: "Rebecca", lastName: "Parsons"});
复制代码
【方法六】拆分阶段
一句话——将把一大段行为拆分成多个顺序执行的阶段。
用途
当看见一段代码在同时处理两件不同的事, 可以把它拆分成各自独立的模块, 因为这样到了需要修改的时候, 就可以单独处理每个模块。
// ==================重构前==================
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, quantity, 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;
}
【方法七】拆分循环
一句话——将一个循环拆分成多个循环。
用途
当遇到一个身兼数职的循环时可以将循环拆解,让一个循环只做一件事情, 那就能确保每次修改时你只需要理解要修改的那块代码的行为就可以了。该行为可能会被质疑,因为它会迫使你执行两次甚至多次循环,实际情况是,即使处理的列表数据更多一些,循环本身也很少成为性能瓶颈,更何况拆分出循环来通常还使一些更强大的优化手段变得可能。
// ==================重构前==================
const people = [
{ age: 20, salary: 10000 },
{ age: 21, salary: 15000 },
{ age: 22, salary: 18000 }
]
let youngest = people[0] ? people[0].age : Infinity;
let totalSalary = 0;
for (const p of people) {
// 查找最年轻的人员
if (p.age < youngest) youngest = p.age;
// 计算总薪水
totalSalary += p.salary;
}
console.log(`youngestAge: ${youngest}, totalSalary: ${totalSalary}`);
// ==================重构后==================
const people = [
{ age: 20, salary: 10000 },
{ age: 21, salary: 15000 },
{ age: 22, salary: 18000 }
]
let totalSalary = 0;
for (const p of people) {
// 只计算总薪资
totalSalary += p.salary;
}
let youngest = people[0] ? people[0].age : Infinity;
for (const p of people) {
// 只查找最年轻的人员
if (p.age < youngest) youngest = p.age;
}
console.log(`youngestAge: ${youngest}, totalSalary: ${totalSalary}`);
// ==================提炼函数==================
const people = [
{ age: 20, salary: 10000 },
{ age: 21, salary: 15000 },
{ age: 22, salary: 18000 }
]
console.log(`youngestAge: ${youngestAge()}, totalSalary: ${totalSalary()}`);
function totalSalary() {
let totalSalary = 0;
for (const p of people) {
totalSalary += p.salary;
}
return totalSalary;
}
function youngestAge() {
let youngest = people[0] ? people[0].age : Infinity;
for (const p of people) {
if (p.age < youngest) youngest = p.age;
}
return youngest;
}
// ==================使用工具类进一步优化==================
const people = [
{ age: 20, salary: 10000 },
{ age: 21, salary: 15000 },
{ age: 22, salary: 18000 }
]
console.log(`youngestAge: ${youngestAge()}, totalSalary: ${totalSalary()}`);
function totalSalary() {
return people.reduce((total,p) => total + p.salary, 0);
}
function youngestAge() {
return Math.min(...people.map(p => p.age));
}
【方法八】拆分变量
一句话——将一个变量拆分成两个或多个变量。
用途
如果变量承担多个责任, 它就应该被替换为多个变量, 每个变量只承担一个责任。
// ==================重构前==================
let temp = 2 * (height + width);
console.log(temp);
temp = height * width;
console.log(temp);
// ==================重构后==================
const perimeter = 2 * (height + width);
console.log(perimeter);
const area = height * width;
console.log(area);
【方法九】分解条件表达式
一句话——将条件表达式提炼成函数。
用途
在带有复杂条件逻辑的函数中,往往可以将原函数中对应的代码改为调用新函数。
对于条件逻辑, 将每个分支条件分解成新函数可以带来的好处:
-
提高可读性
-
可以突出条件逻辑, 更清楚地表明每个分支的作用
-
突出每个分支的原因
// ==================重构前================== // 计算一件商品的总价,该商品在冬季和夏季的单价是不同的 if (!aDate.isBefore(plan.summerStart) && !aDate.isAfter(plan.summerEnd)) charge = quantity * plan.summerRate; else charge = quantity * plan.regularRate + plan.regularServiceCharge; // ==================重构后================== if (summer()) charge = summerCharge(); else charge = regularCharge(); function summer() { return !aDate.isBefore(plan.summerStart) && !aDate.isAfter(plan.summerEnd); } function summerCharge() { return quantity * plan.summerRate; } function regularCharge() { return quantity * plan.regularRate + plan.regularServiceCharge; } // 进一步优化(使用三元运算符) charge = summer() ? summerCharge() : regularCharge(); function summer() { return !aDate.isBefore(plan.summerStart) && !aDate.isAfter(plan.summerEnd); } function summerCharge() { return quantity * plan.summerRate; } function regularCharge() { return quantity * plan.regularRate + plan.regularServiceCharge; }