FE-DSHUI / DSHUI

前端王者小分队读书会
4 stars 1 forks source link

《重构:重构手法-提炼函数,内联函数,提炼变量,内联变量》——2021-02-22 #49

Open isbaselvy opened 3 years ago

isbaselvy commented 3 years ago

第一组重构

提炼函数

function printOwing(invoice) {
 let outstanding = 0;
    // banner
 console.log("***********************");
 console.log("**** Customer Owes ****");
 console.log("***********************");

 // 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);

 //print details
 console.log(`name: ${invoice.customer}`);
 console.log(`amount: ${outstanding}`);
 console.log(`due: ${invoice.dueDate.toLocaleDateString()}`);
}
 function printOwing(invoice) {
    let outstanding = 0;
    printBanner();

    // 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);

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

function printBanner() {
    console.log("***********************");
    console.log("**** Customer Owes ****");
    console.log("***********************");
}

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()}); }


    - 对局部变量再赋值

        - 源函数的参数被赋值,应该将其变成临时变量。
1.变量只在被提炼代码段中使用:将临时变量的声明移到被提炼代码段中,然后一起提炼出去
2.被提炼代码段之外的代码也使用了这个变量:
返回修改后的值
        - 代码:计算outstanding函数

function printOwing(invoice) { printBanner(); // calculate outstanding let outstanding = calculateOutstanding(invoice); // record due date recordDueDate(invoice); // print Details printDetails(invoice, 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()}); }

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

### 内联函数(提炼函数的逆向)

- 动机

    - 本书经常以简短的函数表现动作意图,这样会使代码更清晰易读。但有时候你会遇到某些函数,其内部代码和函数名称同样清晰易读。若真如此,你就应该去掉这个函数,直接使用其中的代码。
另一种需要使用内联函数的情况是:我手上有一群组织不甚合理的函数。可以将它们都内联到一个大型函数中,再以我喜欢的方式重新提炼出小函数。

如果代码中有太多间接层,使得系统中的所有函数都似乎只是对另一个函数的简单委托,造成我在这些委托动作之间晕头转向,那么我通常都会使用内联函数。当然,间接层有其价值,但不是所有间接层都有价值。通过内联手法,我可以找出那些有用的间接层,同时将无用的间接层去除。

- 做法

    - 检查函数,确定它不具多态性。
如果该函数属于一个类,并且有子类继承了这个函数,那么就无法内联。
找出这个函数的所有调用点。
将这个函数的所有调用点都替换为函数本体。
每次替换之后,执行测试。
不必一次完成整个内联操作。如果某些调用点比较难以内联,可以等到时机成熟后再来处理。
删除该函数的定义
注:不适合递归,多返回点,内联值另一个对象而该对象无法访问函数等复杂情况

- 示例

    - 初始代码

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; }


### 提炼变量

- 动机

    - 表达式复杂而难以阅读,这种情况下,可以将其分解为合理命名的形式,便于更好的理解这部分逻辑。如果改变量仅在当前函数有意义,则可只提炼,若需再更宽的范围可以访问,则考虑将其暴露出来,通常以函数的形式

- 做法

    - 1.确认要提炼的表达式没有副作用。
2.声明一个不可修改的变量,把你想要提炼的表达式复制一份,以该表达式的结果值给这个变量赋值。
3.用这个新变量取代原来的表达式。
4.测试。

- 示例

    - 初始代码

        - function price(order) {
    //price is base price - quantity discount + shipping
    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;
}

    - 在一个类中

        - 这些变量名所代表的概念,适用于整个 Order 类,而不仅仅是“计算价格”的上下文。既然如此,我更愿意将它们提炼成方法,而不是变量。
        - 初始代码

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);
}

}



### 内联变量(提炼变量的逆向)

- 动机

    - 在一个函数内部,变量能给表达式提供有意义的名字,因此通常变量是好东西。但有时候,这个名字并不比表达式本身更具表现力。还有些时候,变量可能会妨碍重构附近的代码。若果真如此,就应该通过内联的手法消除变量。

- 做法

    - 检查确认变量赋值语句的右侧表达式没有副作用。
如果变量没有被声明为不可修改,先将其变为不可修改,并执行测试。
这是为了确保该变量只被赋值一次。
找到第一处使用该变量的地方,将其替换为直接使用赋值语句的右侧表达式。
测试。
重复前面两步,逐一替换其他所有使用该变量的地方。
删除该变量的声明点和赋值语句。
测试。

- 示例

    - 初始代码

        - let basePrice = anOrder.basePrice;
return (basePrice > 1000);

    - 内联后代码

        - return anOrder.basePrice > 1000;