《重构》读后感(第五章,第六章)

第五章:介绍重构名录。

本章没有过多内容,主要是对本书后续重构内容的结构的说明。
后边介绍的重构手法,每个都有如下五部分:
1、名称:要建造一个重构词汇表,名称是很重要的。
2、速写:这部分可以帮助你更快找到你所需要的重构手法。
3、动机:为你介绍“为什么要做这个重构”和“什么情况下不该做这个重构”
4、做法:简明扼要的一步一步介绍如何进行此重构。
5、范例:以一个十分简单的例子来说明此重构手法如何运作。

这份重构名录只是记录那些作者认为最值得记录的重构手法。因为它们都是最常用的,值得命名和介绍,有些重构没有收录是因为太小,太简单,没必要多加赘述,还有一些是因为用的很少,或者于其他的重构手法相似。
对于后文将要讲到的每个重构,逻辑上来说,都有一个反向重构。

第六章:第一组重构(最有用的重构)。

一、提炼函数
反向重构:内联函数
动机:【将意图与实现分开】。如果你需要花时间浏览一段代码才能弄清它到底在干什么,那么就应该将其提炼到一个函数中,并根据它所做的事为其命名。以后再读到这段代码时,一眼就能看出这个函数的用途,而不需要关心内部如何实现。

具体展现:

// 重构前
function printOwing(invoice) {
    printBanner();
    let outstanding = calculateOutstanding();

    //print details
    console.log('name: ${invoice.customer}');
    console.log('amount: ${outstanding}');
}

// 重构后
function printOwing(invoice) {
    printBanner();
    let outstanding = calculateOutstanding();
    printDetails(outstanding);

    function printDetails(outstanding) {
        console.log('name: ${invoice.customer}');
        console.log('amount: ${outstanding}');
    }
}

二、内联函数
反向重构:提炼函数
动机:1、函数内部代码和函数名称一样清晰易读。2、一群组织不甚合理的函数,可以将它们都内联到一个大型函数中,然后再提炼。3、代码有太多的间接层(委托),不是所有间接层都有价值。

具体展现:

// 重构前
function getRating(driver) {
    return moreThanFiveLateDeliveries(driver) ? 2 : 1;
}

function moreThanFiveLateDeliveries(driver) {
    return driver.numberOfLateDeliveries > 5;
}

// 重构后
function getRating(driver) {
    return (driver.numberOfLateDeliveries > 5) ? 2 : 1;
}

三、提炼变量
反向重构:内联变量
动机:表达式可能非常复杂而难以阅读,这种情况下,局部变量可以帮助我们将表达式分解为比较容易管理的形式。

具体展现:

// 重构前
return order.quantity * order.itemPrice - 
Math.max(0, order.quantity - 500) * order.itemPrice * 0.05 + 
Math.min(order.quantity * order.itemPrice * 0.1, 100);

// 重构后
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;

四、内联变量
反向重构:提炼变量
动机:有时候变量名并不比表达式本身更具表现力,还有些时候,变量可能会严重妨碍重构附近的代码。

具体展现:

// 重构前
let basePrice = anOrder.basePrice;
return (basePrice > 1000);

// 重构后
return anOrder.basePrice > 1000;

五、改变函数声明
别名:函数改名
动机:函数是我们将程序拆成小块的主要方式。函数声明则展示了如何讲这些小块组合在一起工作,因此函数的名字和其参数就非常重要。

具体展现:

// 重构前
function circum(radius) { ... }

// 重构后
function circumference(radius) { ... }

六、封装变量
动机:函数只有一种用法,就是调用。数据就要麻烦很多,因为没有转发机制。把数据移走就必须修改所有对其的引用代码。当数据的可访问范围很小时,还不成问题。但如果可访问范围变大,重构难度就随之增大,这也就是为什么说全局数据是大麻烦的原因。
所以,要搬移一处被广泛使用的数据,最好的办法就是先以函数的形式封装所有对该数据的访问,这样就把“重新组织数据”的困难任务转化为了“重新组织函数”这个相对简单的任务。另外:封装数据能提供一个清晰的观测点,可由此监控数据的变化和使用情况。

具体展现:

// 重构前
let defaultOwner = {firstName: "Martin", lastName: "Fowler"};

// 重构后
let defaultOwnerData = {firstName: "Martin", lastName: "Fowler"};
export function defaultOwner()       { return defaultOwnerData; }
export function setDefaultOwner(arg) { defaultOwnerData = arg; }

七、变量改名
动机:好的命名是整洁代码的核心。变量可以很好的解释一段程序在干什么。适用范围越广,名字的好坏就越重要。

具体展现:

// 重构前
let a = height * width;

// 重构后
let area = height * width;

八、引入参数对象
动机:一组数据项总是结伴同行,出没于一个又一个函数,这就是所谓的数据泥团,将数据组织成结构就是一件有价值的事情,它会催生代码中更深层次的改变,改变代码的概念图景,将这些数据结构提升为新的抽象概念,可以帮助我们更好的理解问题域。

具体展现:

// 重构前
function amountInvoiced(startDate, endDate) { ... }
function amountReceived(startDate, endDate) { ... }
function amountOverdue(startDate, endDate) { ... }

// 重构后
function amountInvoiced(aDateRange) { ... }
function amountReceived(aDateRange) { ... }
function amountOverdue(aDateRange) { ... }

九、 函数组合成类
动机:如果发现一组函数形影不离的操作同一块数据,就可以组建一个类了。类能明确的给这些函数提供一个共用的环境,在对象内部调用就可以少传很多参数,简化函数调用,同时也可以更方便的传递给系统的其他部分。这个重构还给我们一个机会,去发现其他的计算逻辑,将它们也重构到这个类当中。

具体展现:

// 重构前
function base(aReading) { ... }
function texableCharge(aReading) { ... }
function calculateBaseCharge(aReading) { ... }

// 重构后
class Reading {
    base() { ... }
    texableCharge() { ... }
    calculateBaseCharge() { ... }
}

十、函数组合变换
动机:在软件中,经常需要把数据“喂”给一个程序,让它再计算出各种派生信息。这些派生数值可能会在不同的地方用到,因此这些计算逻辑经常在用到派生数据的地方重复。这样,我们就可以把所有计算派生数据的逻辑收拢到一处,以后可以在固定的地方找到和更新这些逻辑,避免重复。
函数组合变换的替代方案是上一条的内容:函数组合成类。不过两者有一个重要的区别:如果代码中会对源数据做更新,那么使用类更好一些;如果使用变换,派生数据会被存储在新生成的记录中,一旦源数据被修改,就会遭遇数据不一致。

具体展现:

// 重构前
function base(aReading) { ... }
function texableCharge(aReading) { ... }

// 重构后
function enrichReading(argReading) {
    const aReading = _.cloneDeep(argReading);
    aReading.baseCharge = base(aReading);
    aReading.texableCharge = texableCharge(aReading)
    return aReading;
}

十一、拆分阶段
动机:一段代码在同时处理两件不同的事。

具体展现:

// 重构前
const orderData = orderString.split(/\s+/);
const productPrice = priceList[orderData[0].split("-")[1]];
const orderPrice = parseInt(orderData[1]) * productPrice;

// 重构后
const orderRecord = priceOrder(order);
const orderPrice = price(orderRecord, priceList);

function parseOrder(aString) {
    const values = aString.split(/\s+/);
    return ({
        productID: values[0].split("-")[1]],
        quantity: parseInt(values[1]),
    });
}

function price(order, priceList) {
    return order.quantity * priceList[orderData.productID];
}