《重构,改善既有代码的设计》读书笔记三:重新组织函数

在写程序代码时,编写函数几乎是不可缺少的,所以对函数的重构,使其更恰当的包装代码是很有必要的。几乎所有时刻,问题来源于过长的函数(Long Method),由于他们包含太多的信息,这些信息又被函数错综复杂的逻辑所掩盖,不易鉴别,所以需要我们考虑如何对这样的函数进行重构,下面就列举一些常用的重构函数的手法,大家可以在自己的重构过程中灵活的运用。

1、提炼函数(Extract Method)

如果出现过长的函数时(具体多长的函数算长,可能没有统一的标准,有些公司会要求一个函数不能超过80行,但是长度不是关键问题,关键在于函数名称和函数体之间的语义距离),或者一段注释才能读者理解代码用途时,那么可将这个函数进行适当的提炼出多个简短并且命名良好的函数。这么做的好处显而易见,首先,粒度小的函数将来更容易被复用;其次,这会使高层调用他们的函数读起来像一系列的注释,清晰明了。

具体做法

创造一个新函数,根据这个函数的用途去命名(以它“做什么”来命名,而不是“怎么做”)
将提炼出的代码从源函数复制到新建的目标函数中去
在源函数中将被提炼的代码替换为对新的目标函数的调用
编译、测试
2、内联函数(Inline Method)

       如果你发现有一群组织不是很合理的函数,你可以将他们内联到一个大的函数中,再从中提炼出组织合理的小型函数,这样做往往会取得不错的效果。把所要的函数的所有调用对象的函数内容都内联到函数对象中,比起既要移动一个函数,又要移动它所调用的其他所有函数,将整个大型函数作为整体来移动会比较简单。间接层有其价值,但不是所有的间接层都有价值。过多的间接层会造成读者在各个间接调用函数之间来回跳转晕头转向,造成过高的理解成本。

       具体做法

     (1)检查函数,确定其不具有多态性

              如果有子类继承了这个函数,就不要将此函数内联,因为子类无法覆写一个根本不存在的函数

     (2)找出这个函数所有被调用点

     (3)将这个函数所有被调用点都替换为函数本体

      (4)编译、测试,删除该函数的定义

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

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

example:

double basePrice = _quantity * _itemPrice;
if(basePrice>1000) {
    return basePrice * 0.95;
} else {
    return basePrice * 0.98;
}
Replace Temp with Query=====>

double basePrece() {
    return _quantity * _itemPrice;
}
 
if(basePrice()>1000) {
    return basePrice() * 0.95;
} else {
    return basePrice() * 0.98;
}
这么做的好处在于:临时变量只能在所属函数内使用,由于临时变量只在所属函数内可见,所以它们可能会驱使你写出更长的函数,因为只有这样你才能访问到需要的临时变量。如果把临时变量作为一个查询,那么同一个类中所有函数都将可以获得这份信息。这样会给你带来很大的帮助,使你能够为这个类编写更清晰的代码。


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

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

example:

if(platform.toUpperCase().indexOf("MAC") > -1 
&& (browser.toUpperCase().indexOf("IE")>-1) 
&& (wasInitialized() && resize>0)) {
    //do something
}
Introduce Explaining Variable======>

final boolean isMacOs = platform.toUpperCawe().indexOf("MAC") > -1;
final boolean isIEBroswer = browser.toUppoerCase().indxeOf("IE") > -1;
final boolean wasResized = resize>0;
if(isMacOs && isIEBrowser && wasInitialized() && wasResized) {
 
    //do something
}
5、分解临时变量(Split Temporary Variable)

代码中常常用一个临时变量去保存一段冗长代码的计算结果,以便后面使用。这种临时变量应该只被赋值一次,如果它们被赋值超过一次,就意味着这个临时变量在函数中承担了一个以上的责任。如果临时变量承担过多的责任,它就应该被分解为多个临时变量,每个变量只承担一个责任,同一个临时变量承担两件不同的事情会令代码阅读者感到迷惑。

example:

double temp = 2 * (_height + _width);
System.out.prientln(temp);
temp = _height * _width;
System.out.prientln(temp);
Split Temporary Variable=====>

final double perimeter = 2 * (_height + _width);
System.out.prientln(perimeter);
final double area = _height + _width;
System.out.prientln(area);
6、移除对参数的赋值(Remove Assignments to Parameters)

对入参进行赋值的做法降低了代码的清晰度,而且混用了按值传递和按引用传递这两种参数传递方式。Java只采用按值传递方式,在Java中,不要对参数进行赋值,如果你看到手上的代码这样做了,请对此进行Remove Assignments to Parameters重构。

example:

int discount(int inputVal, int quantity, int yearToDate) {
if(inputVal>50) {
    inputVal = 2;
    }
}
Remove Assignments to Parameters=====>

int discount(int inputVal, int quantity, int yearToDate) {
    int result = inputVal;
    if(inputVal > 50) {
        result = 2;
    }
}
7、以函数对象取代函数(Replace Method with Method Object)

如果有这样一个大型函数,使用了很多局部变量导致你无法对该函数进行抽取重构(Extract Method),这时可以将这个函数放进一个单独对象中,如此一来局部变量就成了对象内部的字段,然后就可以在同一个对象中将这个大型函数进行分解为多个小型函数。

example:

class Account {
    int gamma(int inputVal,int yearToDate) {
        int importantValue1 = (inputVal * quantity) + delta();
        int importantValue2 = (inputVal * yearToDate) + 100;
        if((yearToDate - importantValue1)>100) {
            importantValue2 = 20;
        };
        int importantValue3 = importantValue2 * 7;
        return importantValue3 -2 * importantvalue1;
    }
}
Replace Method with Method Object=====>

class Gamma {
    private final Acccount _account;
    private int inputVal;
    private int quantity;
    private int yearToDate;
    private int importantValue1;
    private int importantValue2;
    private int importantValue3;
 
    PriceCalculator(Acccount account; int inputValArg,int quantityArg,int yearToDateArg) {
        this._account = account;
        this.inputVal = inputValArg;
        this.quantity = quantityArg;
        this.yearToDate = yearToDateArg;
    }
 
    int compute() {
       int importantValue1 = (inputVal * quantity) + _account.delta();
        int importantValue2 = (inputVal * yearToDate) + 100;
        if((yearToDate - importantValue1)>100) {
            importantValue2 = 20;
        };
        int importantValue3 = importantValue2 * 7;
        return importantValue3 -2 * importantvalue1;
    }
    
}
class Account {
    int gamma(int inputVal,int quantity,int yearToDate) {
        return nw Gamma(this,inputVal,quantity,yearToDate).tompute();
    } 
}
8、替换算法(Substitute Algorithm)

就是将某个复杂晦涩的算法替换为另一个更清晰的算法。解决同一个问题的算法往往不只有一种,但是如果能以更加清晰的算法去解决问题,就应该以这种方式去取代复杂的方式。

以上几点就是对函数进行重构的一些总结,对于函数的重构手法可能远不止以上列举的几点,重要的是要灵活的去使用而不是生搬硬套,如果你有更好的手法,欢迎在下方留言讨论。
————————————————
版权声明:本文为CSDN博主「李白csdn」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zjl_pcw/article/details/108487813

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值