重构-改善既有代码的设计读书笔记(六)

重新组织函数

6.1 Extract Method(提炼函数)

你有一段代码可以被组织在一起并独立出来。将这段代码放进一个独立函数中,并让函数名称解释该函数的用途。
将过长函数拆分为命名良好的函数:

  • 如果每个函数的粒度都很小,那么函数被复用的机会就更大。

  • 会使高层函数就像一系列注释。

  • 如果函数都是细粒度,那么函数的覆写也会更容易些。
    做法:

    1. 创建一个新函数,以函数“做什么”来而不是“怎么做”来命名。
    2. 将提炼出的代码从源函数复制到新建的目标函数中。
    3. 检查提炼出的代码,看看其中是否引用了“作用域限于源函数”的变量。
    4. 检查是否有“仅用于被提炼代码段”的临时变量。如果有,中 目标函数中将它们声明为临时变量。
    5. 检查被提炼代码段,看看是否有任何局部变量的值被它改变。如果一个临时变量的值被修改了,看看是否可以将被提炼代码段处理为一个查询,并将结果赋值给相关变量。如果很难这样做,或如果被修改的变量不止一个,你就可能需要先使用Split Temporary Variable(6.6),然后再尝试提炼。也可以使用Replace Temp Query(6.4)将临时变量消灭掉。
    6. 将被提炼代码段中需要读取的局部变量,当作参数传给目标函数。
    7. 在源函数中,将被提炼代码段替换为对目标函数的调用。

6.2 Inline Method(内联函数)

一个函数的本体与名称同样清楚易懂。将函数调用点改为函数本体。
对一起组织不甚合理的函数,可以将它们内联到一个大型函数中,再从中提炼出组织合理的小型函数。
如果系统中有太多的函数间的简单委托,可以使用Inline Method去掉无用的间接层。
做法:
1. 检查函数,确定它不具多态性。
2. 找出这个函数的所有调用点,将调用点替换为函数本体。

6.3 Inline Temp(内联临时变量)

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

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

to

return anOrder.basePrice() > 1000;

Inline Temp多半是作为Replace Temp with Query的一部分使用的。
做法:
1. 检查给临时变量赋值的语句,确保等号右边的表达式没有副作用。
2. 如果这个临时变量并未被声明为final,就将它声明为final。
3. 找到该临时变量的所有引用点,将它们替换为“为临时变量赋值”的表达式。

6.4 Replace Temp with Query(以查询取代临时变量)

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

 double basePrice = quantity*itemPrice;
 if(basePrice > 1000)
     return basePrice*0.95;
else
    return basePrice*0.98;

to

 if(basePrice() > 1000)
     return basePrice()*0.95;
else
    return basePrice()*0.98;

double basePrice(){ 
    return quantity*itemPrice;
}

将临时变量替换为一个查询,那么一个类中的所有函数都可以获得这份信息,可以有效避免长函数的出现。
做法:
1. 找出只被赋值一次的临时变量。
如果某个临时变量被赋值超过一次,考虑使用Split Temporary Variable(6.6)将它分割成多个变量。
2. 将该临时变量声明为final。
3. 将“对该临时变量赋值”的语句的等号右侧部分提炼到一个独立函数中。
4. 在该临时变量身上使用Inline Temp(6.3)。
对于循环累加值 这种情况,应该针对每个累加值重复一遍循环(由此可能带来的性问题可以以后再优化),这样可以将所有临时变量都替换为查询。

6.5 Introduce Explaining Variable(引入解释性变量)

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

if((platform.toUpperCase().indexOf("MAC") > -1) && (browser.toUpperCase().indexOf("IE") > -1) && wasInitialized() && resize > 0)
    {...}

to

final boolean isMacOS = platform.toUpperCase().indexOf("MAC") > -1;
final boolean isIEBrowser = browser.toUpperCase().indexOf("IE") > -1;
final boolean wasResized = resize > 0;
if(isMacOS && isIEBrowser && wasInitialized() && wasResized)
    {...}

动机:

  • 可以用这项重构,以一个命名良好的临时变量来解释条件子句的意义。

  • 在较长算法中,可以运用临时变量来解释每一步运算的意义。

做法:
1. 声明一个final临时变量,将待分解之复杂表达式中的一部分动作的运算结果赋值给它。
2. 将表达式中的“运算结果”这一部分,替换为上述临时变量。
3. 重复上述过程,处理表达式的其他部分。

6.6 Split Temporary Variable(分解临时变量)

你的程序有某个临时变量被赋值超过一次,它既不是循环变量,也不拥有收集计算结果,针对每次赋值,创建一个独立、对应的临时变量。

double temp=2*(height+width);
system.out.printlt(temp);
temp = height*width;
system.out.println(temp);

to

final double perimeter=2*(height+width);
system.out.printlt(perimeter);
final area= height*width;
system.out.println(area);

除了循环变量和结果收集变量这两种,其他的临时变量应该只被赋值一次。如果超过一次,说明这个变量承担了多个责任,会降低代码可读性。

做法:
1. 在待分解临时变量的声明及其第一次赋值处,修改其名称。
2. 将新的临时变量声明为final。
3. 以该临时变量的第二次赋值动作为界,修改此前对该临时变量的所有引用点,让它们引用新的临时变量。
4. 在第二次赋值处,重新声明原先那个临时变量。
5. 逐次重复上述过程,每次都在声明处对临时变量改名,并修改下次赋值之前的引用点。

6.7 Remove Assignments to Parameters(移除对参数的赋值)

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

int discount(int inputVal, int quantity, int yearToDate) {  
    if(inputVal > 50)
        inputVal -= 2;
}

to

int discount(int inputVal, int quantity, int yearToDate) {  
    int result = inputVal;
    if(inputVal > 50)
        result -= 2;
}

对参数赋值,会降低代码的清晰度,而且混用了按值传递和按引用传递这两种参数传递方式。
做法:
1. 建立一个临时变量,把待处理的参数赋值给它。
2. 以“对参数的赋值”为界,将其后所有对此参数的引用点,全部替换为“对此临时变量的引用”。
3. 修改赋值语句,使其改为对新建之临时变量赋值。

6.8 Replace Method with Method Object(以函数对象取代函数)

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

class Order...
    double price() {
        double primaryBasePrice;
        double secondaryBasePrice;
        double tertiaryBasePrice;
        // long computation;
        ...
    }

to

class Order...{ 
    doubel price() {
        return new PriceCalculator(this).compute();
    }
}

class PriceCalculator { 
    double primaryBasePrice;
    double secondaryBasePrice;
    double tertiaryBasePrice;

    double compute() {...}
}

做法:
1. 建立一个新类,根据待处理函数的用途,为这个类命名。
2. 在新类中建立一个final字段,用以保存原先大型函数所在的对象。我们将这个字段成为“源对象”。同时,针对原函数的每个临时变量和每个参数,在新类中建立一个对应的字段保存之。
3. 在新类中建立一个构造函数,接收源对象及原函数的所有参数作为参数。
4. 中新类中建立一个compute()函数。
5. 将原函数的代码复制到compute()中。如果需要调用源对象的任何函数,请通过源对象字段调用。
6. 将旧函数的函数本体替换为新类调用compute()函数的语句。

6.9 Substitute Algorithm(替换算法)

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

每天进步一点_点

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值