《重构 改善代码既有设计》读书笔记(二)

《重构 改善代码既有设计》第六章内容读书笔记

重新组织函数往往面对的问题是过长的函数,使用的手法是提炼函数

难点1:局部变量

  • 查询取代局部变量
  • 分解临时变量
  • 以函数对象取代函数

难点2:参数

  • 移除对参数的赋值

提炼函数将长函数变得足够清晰后,便可使用替换算法优化其中的算法,或者使用内联函数回退掉不必要的函数提炼

提炼函数

有一段代码可以被组织在一起并独立出来:将这段代码放进一个独立函数中,并让函数名称解释该函数的用途

动机

用于过长的函数或一段需要注释才能让人理解的代码

目的是使函数能够自注释,强化代码清晰度。重构的结果就是函数越来越多,每个独立代码段都由函数名自注释。

做法

对局部变量的处理:

  • 被提炼的函数仅读取局部变量 / 通过对象或引用传递的方式修改局部变量,直接将局部变量作为参数传入即可
  • 被提炼函数需要对局部变量重新赋值
    • 变量仅在新函数中使用:将声明移入
    • 变量在原函数中有使用:用参数传入上文,返回值传给下文
    • 复杂情况:需要返回的变量不止一个,需要挑选另一块代码重新提炼
  • 以查询取代局部变量、以函数对象取代函数

范例

重构前:

void printOwing(double previousAmount) {
    Enumeration e = _oeders.elements();
    double outstanding = previousAmount * 1.2;
    
    printBanner();
    
    // calculate outstanding
    while (e.hasMoreElements()) {
        Order each = (Order) e.nextElements();
        outstanding += each.getAmount();
    }
    
    printDetails(outstanding); // 参数传入
}

重构后:

void printOwing(double previousAmount) {
    printBanner();
    double outstanding = getOutstanding(previousAmount * 1.2);
    printDetails(outstanding);
}

double getOutstanding(double initialValue) { // 参数传入上文,返回值传出下文
    double result = initialValue;
    Enumeration e = _oeders.elements(); // 声明移入
    while (e.hasMoreElements()) {
        Order each = (Order) e.nextElements();
        result += each.getAmount();
    }
    return result;
}

内联函数

一个函数的本体与名称同样清楚易懂:在函数调用点插入函数本体,然后移除该函数。

动机

  • 函数的本体与名称同样清楚易懂,就没有必要引入一个间接层
  • 一群组织不甚合理的函数,可以先把它们内联到一个大函数中,再进行提炼函数以函数对象取代函数
  • 使用了太多间接层,系统中几乎所有函数都只是一个简单委托。间接层有其价值,但不是所有的间接层都有价值。

内联临时变量

你有一个临时变量,只被一个简单表达式复制一次,而它妨碍了其他重构手法:将所有对该变量的引用动作,替换为对它赋值的那个表达式自身。

将所有对该变量的引用动作替换为对它赋值的那个表达式本身。常作为以查询取代临时变量的一个步骤

重构前:

double basePrice = anOrder.basePrice();
return (basePrice > 1000);

重构后:

return (anOrder.basePrice() > 1000);

以查询取代临时变量

你的程序以一个临时变量保存某一表达式的运算结果:将这个表达式提炼到一个独立函数中。将这个临时变量的所有引用点替换为对新函数的调用。此后,新函数就可被其他函数使用。

动机

临时变量的问题在于,它们是暂时的,而且只能在所属函数内使用,会驱使你写出更长的函数。如果把临时变量替换为一个查询,那么同一个类中的所有函数都可以获得这一信息(复用)

往往在提炼函数前运用。比较棘手的情况是,临时变量被赋值了多次,或者赋值方式受其他条件影响。此时可能需要先运用分解临时变量查询和修改函数分离使情况变得简单一些,然后再替换临时变量。

范例

重构前:

double getPrice() {
    int basePrice = _quantity * _itemPrice;
    double discountFactor;
    if (basePrice > 1000) discountFactor = 0.95;
    else discountFactor = 0.98;
    return basePrice * discountFactor;
}

重构后:

double getPrice() {
    return basePrice() * discountFactor();
}

private int basePrice() {
    return _quantity * _itemPrice;
}

private double discountFactor() {
    if (basePrice() > 1000) return 0.95;
    return 0.98;
}

引入解释性变量

你有一个复杂的表达式:将该复杂表达式(或其中一部分)的结果放进一个临时变量,以此变量名称来解释表达式的用途。

动机

表达式可能非常复杂而难以阅读,这种情况下,临时变量可以帮助你将表达式分解为比较容易管理的形式。

  • 条件逻辑:使用临时变量将每个条件子句提炼出来
  • 较长算法:运用临时变量来解释每一步运算的意义

范例

重构前:

double price() {
    // base price - quantity discount + shipping
    return _quantity * _itemPrice - 
        Math.max(0, _quantity - 500) * _itemPrice * 0.05 + 
        Math.min(_quantity * _itemPrice * 0.1. 100.0);
}

使用引入解释性变量

double price() {
    final double basePrice = _quantity * _itemPrice;
    final double quantityDiscount = Math.max(0, _quantity - 500) * _itemPrice * 0.05;
    final double shipping = Math.min(basePrice * 0.1, 100.0);
    return basePrice - quantityDiscount + shipping;
}

使用提炼方法

double price() {
    return basePrice() - quantityDiscount() + shipping();
}

private double basePrice() {
    return _quantity * _itemPrice;
}

private double quantityDiscount() {
    return Math.max(0, _quantity - 500) * _itemPrice * 0.05;
}

private double shipping() {
    return Math.min(basePrice() * 0.1, 100.0);
}

两者都是为了实现代码自注释,提炼函数的结果更清晰,但是有时工作量会很大,这时候可以考虑使用引入解释性变量

分解临时变量

你的程序有某个临时变量被赋值超过一次,它既不是循环变量,也不用于手机计算结果:针对每次赋值,创造一个独立、对应的临时变量。

动机

除了循环变量和用于收集结果的临时变量(补充:go语言err返回值,也经常被多次使用且不会造成糊涂),其他临时变量往往用于保存一段冗长代码的运算结果。这些临时变量只应该被赋值一次。承担单一的责任。同一临时变量城改两件不同的事情,会使阅读者糊涂/

收集结果的变量,一个重要特征是之后的赋值语句为 [i = i + X] 形式

移除对参数的赋值

代码对一个参数进行赋值:以一个临时变量取代该参数的位置

降低了代码清晰度,混用了按值传递和按引用传递两种方式。有“出参数”的语言可例外,但是还是应减少出参数,或者对出参数用宏明确标识。

可以为参数加上final,让编译器帮助检查是否对参数做了修改。

以函数对象取代函数

你有一个大型函数,其中对局部变量的使用是你无法参与提炼函数:将这个函数放进一个单独对象中,如此一来局部变量就成了对象中的字段。然后你可以在同一个对象中将这个大型函数分解为多个小函数。

将函数上下文提取到对象中

动机

局部变量的存在增加了函数分解的难度。以函数对象取代函数会将所有局部变量都变成函数对象的字段。然后就可以对这个函数对象使用提炼函数创造新函数,将大型函数变小。这时候可以任意分解这个大型函数而不必传递任何参数,因为所有的局部变量都变成了函数对象的字段。

做法

  • 建立一个新类,根据待处理函数的用途为这个类命名
  • 在新类中建立一个final字段,用以保存原来大型函数归属的对象,称“源对象”;同时,针对原函数的每个临时变量和每个参数,在新类中建立一个对应的i段保存
  • 新类中建立一个构造函数,接收源对象以及原函数的所有参数作为参数
  • 新类中建立一个compute()函数
  • 将原函数的代码复制到compute()函数中。在需要调用源对象方法的地方,修改为使用源对象字段调用
  • 编译
  • 旧函数的函数本体替换为创建一个函数对象,并调用compute()方法

替换算法

你想要把某个算法替换为另一个更清晰的算法:将函数本体替换为i另一个算法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值