重构技巧

148 阅读4分钟

💡 根据 遗忘曲线:如果没有记录和回顾,6天后便会忘记75%的内容

读书笔记正是帮助你记录和回顾的工具,不必拘泥于形式,其核心是:记录、翻看、思考

书名重构:改善既有代码的设计(第2版)
作者马丁·福勒(Martin_Fo)
状态阅读中
简介本书包含了代码中容易出现的问题冗余,💩山,代码耦合度高等问题,提出了对应的解决方案。让代码重构从小的点做起,最后实现整个模块的重构。

思维导图

用思维导图,结构化记录本书的核心观点。

重构技巧

1. 提炼函数

通过函数单一原则,将复杂函数按功能提炼出简单函数

使用场景:如果需要花时间浏览一段代码才能弄清楚它到底要干什么,那么这时候就应该将其提炼到一个函数中,并根据它所做的事命名。以后再读这段代码时,一眼就能知道这个函数的用途。

2. 函数参数化

通过参数形式,消除重复函数

如路由跳转等相似方法,可以通过传参封装函数

3. 策略模式

使用策略模式替换if-else或者switch-case

// 重构前代码
function getPrice(tag, originPrice) {
    // 新人价格
    if(tag === 'newUser') {
        return originPrice > 50.1 ? originPrice - 50 : originPrice
    }
    // 返场价格
    if(tag === 'back') {
         return originPrice > 200 ? originPrice - 50 : originPrice
    }
    // 活动价格
    if(tag === 'activity') {
        return originPrice > 300 ? originPrice - 100 : originPrice
    }
}

// 重构后
const priceHandler = {
    newUser(originPrice){
        return originPrice > 50.1 ? originPrice - 50 : originPrice
    },
    back(originPrice){
        return originPrice > 200 ? originPrice - 50 : originPrice
    },
    activity(originPrice){
         return originPrice > 300 ? originPrice - 100 : originPrice
    }
}

function getPrice(tag, originPrice){
    return priceHandler[tag](originPrice)
}

4. 提炼变量

若有的表达式比较复杂,难以阅读,则需要提炼一个简单局部变量将表达式分解存储

// ==================重构前==================
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;
}

5. 拆分阶段

若一大段代码在同时处理两件不同的事,可以将其拆分成多个独立的模块

// ==================重构前==================
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;
}

6. 拆分循环

如果一个循环中做多件事,可以让一个循环只做一件事,性能方面只是由O(N)变为O(2N),一般数据量不是很大的情况下,不会有大的影响。主要是让代码可读性增强

// ==================重构前==================
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));
}

7. 卫语句取代嵌套条件表达式

如果某个条件极其罕见,就应该单独检查该条件,并在该条件为真时立刻从函数中返回。这样的单独检查常常被称为“卫语句”

// ==================重构前==================
function payAmount(employee) {
    let result;
    if(employee.isSeparated) {
        result = {amount: 0, reasonCode:"SEP"};
    }
    else {
        if (employee.isRetired) {
            result = {amount: 0, reasonCode: "RET"};
        }
        else {
            result = someFinalComputation();
        }
    }
    return result;
}

// ==================重构后==================
function payAmount(employee) {
    if (employee.isSeparated) return {amount: 0, reasonCode: "SEP"};
    if (employee.isRetired) return {amount: 0, reasonCode: "RET"};
    return someFinalComputation();
}

相关资料

js代码重构示例