重构-改善既有代码的设计(四):重新组织函数的九种方法

函数过长或者逻辑太混乱,重新组织和整理函数的代码,使之更合理进行封装。

1. Extract Method 提炼函数

提炼函数:(由复杂的函数提炼出独立的函数或者说大函数分解成由小函数组成)你有一段代码可以被组织在一起并独立出来。将这段代码放进一个独立函数,并让函数名称解释该函数的用途。

  1. void printOwing() {   

  2.     //print banner  

  3.     System.out.println(“*********”);  

  4.     System.out.println(“Banner”);  

  5.     System.out.println(“*********”);  

  6.     //print details  

  7.     System.out.println ("name: " + _name);   

  8.         System.out.println ("amount " + getOutstanding());   

  9. }   

640?wx_fmt=png

       void printOwing()  {  

  1.     printBanner();   

  2.     printDetails(getOutstanding());   

  3. }   

  4.   

  5. Void printBanner()  {  

  6.     //print banner  

  7.     System.out.println(“*********”);  

  8.     System.out.println(“Banner”);  

  9.     System.out.println(“*********”);  

  10. }  

  11. <strong>void printDetails (double outstanding)   {   

  12.     System.out.println ("name: " + _name);   

  13.     System.out.println ("amount " + outstanding);   

  14. } </strong>  

过长的函数或者一段需要注释才能让人理解用途的代码,就应该将这段代码放进一个独立函数中。简短而命名良好的函数的好处:

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

2)这会使高层函数读起来就想一系列注释;

3)如果函数都是细粒度,那么函数的覆写也会更容易些。一个函数多长才算合适?长度不是问题,关键在于函数名称和函数本体之间的语义距离。如果提炼可以强化代码的清晰度,那就去做,就算函数名称必提炼出来的代码还长也无所谓。

2. Inline Method 内联函数

内联函数:(直接使用函数体代替函数调用 ) 一个函数调用的本体与名称同样清楚易懂。在函数调用点插入函数体,然后移除该函数

  1. int getRating() {  

  2.     return moreThanfiveLateDeliverise() ? 2 : 1;  

  3. }  

  4. bool moreThanfiveLateDeliverise() {  

  5.     return _numberOfLateLiveries > 5;  

  6. }  

640?wx_fmt=png

                                                           int getRating(){  

  1.      return _numberOfLateLiveries > 5 ? 2 : 1;  

  2. }  

     有时候你会遇到某些函数,其内部代码和函数名称同样清晰易读。也可能你重构了该函数,使得其内容和其名称变得同样清晰。果真如此,你应该去掉这个函数,直接使用其中的代码。间接性可能带来帮助,但非必要的间接性总是让人不舒服。

3.Inline Temp 内联临时变量

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

  1. double basePrice = anOrder.BasePrice();  

  2. return basePrice(>1000);  

                                                           640?wx_fmt=png                     

  1. return (anOrder.BasePrice() >1000);  

        Inline Temp(内联临时变量)多半是作为Replace Temp with Query(以查询取代临时变量)的一部分使用的,所以真正的动机出现在后者那里。唯一单独使用Inline Temp(内联临时变量)情况是:你发现某个临时变量被赋予某个函数调用的返回值。

4.Replace Temp with Query 以查询代替临时变量

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

  1. double basePrice = _quantity*_itemPrice;  

  2. if (basePrice > 1000) {  

  3.     return basePrice * 0.95;  

  4. else   

  5.     return basePrice * 0.98;  

                                                             640?wx_fmt=png

if (basePrice() > 1000)  

  1.     return basePrice() * 0.95;   

  2. else   

  3.     return basePrice() * 0.98;   

  4. ……  

  5. double basePrice() {   

  6.     return _quantity * _itemPrice;   

  7. }   

    临时变量的问题在于:它们是暂时的,而且只能在所属函数内使用。由于临时变量只是在所属函数内可见,所以它们会驱使你写出更长的函数,因为只有这样你才能访问到需要的临时变量。如果把临时变量替换为一个查询,那么同一个类中的所有函数都可以获得这份信息。这将带给你极大帮助,使你能够为这个类编写更清晰地代码。    以查询代替临时变量往往是你运用Extract Method(提炼函数)之前必不可少的一个步骤。局部变量会使代码难以被提炼,所以你应该尽可能把它们替换为查询式。    这个重构手法较为简单的情况是:临时变量只被赋值一次,或者赋值给临时变量的表达式不受其他条件影响。其他情况比较棘手,但也可能发生。你可能需要先运用Split Temporary Variable(分解临时变量)或Separate Query form Modifier(将查询函数和修改函数分离) 使情况变得简单一些,然后再替换临时变量。如果你想替换的临时变量是用来收集结果的)例如循环中的累加值),就需要将某些程序逻辑(例如循环)复制到查询函数去。

5.Introduce Explaining Variable 引入解释性变量

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

  1. if (Platform.ToUpperCass().indexOf("MAC") > -1 && (Browser.ToUpperCass().indexOf("Ie") > -1) && WasInitalized() ) {  

  2.     //do something  

  3.  }  

640?wx_fmt=png                       const bool imMacOs = Platform.ToUpperCass().indexOf("MAC") > -1;  

  1. const bool isIeBrowser = Browser.ToUpperCass().indexOf("Ie") > -1;  

  2. const bool wasInitalized = WasInitalized();  

  3. if (imMacOs && isIeBrowser && wasInitalized)  

  4. {  

  5.     //do something  

  6. }  

    表达式有可能非常复杂而难以阅读。这种情况下,临时变量可以帮助你将表达式分解为比较容易管理的形式。    在条件逻辑中,Introduce Explaining Variable(引入解释性变量)是一个很常见的手法,但是最好尽量使用 Extract Method(提炼函数) 来解释一段代码的意义。毕竟临时变量只在他所处的那个函数才有意义,局限性较大。函数则可以在对象的这个生命中都有用,并且可被其他对象使用。但有时候,当局部变量使Extract Method(提炼函数)难以进行时,就可以使用Introduce Explaining Variable (引入解释性变量).

6. Split Temporary Variable 分解临时变量

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

  1. double temp = 2 + (_height + _width);  

  2. Console.WriteLine(temp);  

  3. temp = _height * _width;  

  4. Console.WriteLine(temp);  

                                                           

640?wx_fmt=png

  1. const double perimeter = 2 + (_height + _width);  

  2. Console.WriteLine(perimeter);  

  3. const double area = _height * _width;  

  4. Console.WriteLine(area);  

        临时变量有各种不同用途,其中某些用途会很自然的导致临时变量被多次赋值。“循环变量”和“结果收集变量”就是典型的例子:循环变量会随循环的每次运行而改变;   结果收集变量负责将“通过这个函数的运算”而构成的某个值收集起来。        除了这2种情况,还有很多临时变量保存一段冗长代码的运算结果,以便稍后使用。这种临时变量应该只被赋值一次。如果它们被赋值超过一次,就意味着它们在函数中承担了一个以上的职责。如果临时变量承担多个责任,它就应该被替换为多个临时变量,每个变量只承担一个责任。同一个临时变量承担2件不同的事情,会令代码阅读者糊涂。

7.Remove Assigments to Parameters 移除对参数的赋值

移除对参数的赋值:(不要对参数赋值)代码对一个 参数赋值。以一个临时变量取代该参数的位置。 

  1. int discount (int inputVal, int quantity, int yearToDate){   

  2.     if (inputVal > 50) inputVal -= 2;   

  3. }  

  640?wx_fmt=png                                              int discount (int inputVal, int quantity, int yearToDate) {   

  1.      int result = inputVal;   

  2.      if (inputVal > 50) result -= 2;   

  3. }  

如果参数是Object,容易误赋值。采用final来防止误用参数:

要清楚“对参数赋值”这个说法的意思。如果你把一个名为foo的对象作为参数传给某个函数,那么“对参数赋值”意味着改变foo,使它引用另外一个对象。如果你在“被传入对象”身上进行什么操作,那没什么问题。这里只针对“foo被改而指向另一个对象”这种情况来讨论。

  1. <span style="font-size:12px;">void aMethod(Object  foo) {  

  2.       foo.modifyInSomeWay();  

  3.       foo = anotherObject;  

  4. }</span>  

这样的做法降低了代码的清晰度,而且混用了按值传递和按引用传递这2种参数传递方式。8. Replace Method with Method object 函数对象取代函数

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

  1. class Order...  

  2. double price() {   

  3.     double primaryBasePrice;  

  4.     double secondaryBasePrice;   

  5.     double tertiaryBasePrice;   

  6.     // long computation; ...   

  7. }   

                                                 640?wx_fmt=png

640?wx_fmt=png

    或者可以采用static method

        局部变量的存在会增加函数分解的难度。如果一个函数之中局部变量泛滥,那么想分解这个函数是非常困难的。Replace Temp with Query (以查询取代临时变量)可以帮助你减轻这一负担,但有时候你会发现根本无法拆解一个需要拆解的函数。这种情况下,应该使用函数对象。

9.Substitute Algorithm 替换算法

替换算法:(函数本体替换为另一个算法)你想要把某个算法替换为另一个更清晰地算法。将函数本体替换为另一个算法。  

  1. String foundPerson(String[] people){   

  2.     for (int i = 0; i < people.length; i++)  {   

  3.         if (people[i].equals ("Don")){  

  4.              return "Don";   

  5.          }  

  6.          if (people[i].equals ("John")) {  

  7.            return "John";   

  8.          }   

  9.          if (people[i].equals ("Kent")){   

  10.                 return "Kent";   

  11.          }   

  12.      }  

  13.      return "";   

  14. }   

640?wx_fmt=png                                                 

String foundPerson(String[] people){   

  1.     List candidates   

  2.              = Arrays.asList(new String[] {"Don",   "John""Kent"});   

  3.     for (int i=0; i<people.length; i++)   

  4.         if (candidates.contains(people[i]))   

  5.              return people[i]; return "";   

  6. }   

      解决问题有好几种方法。算法也是如此。如果你发现做一件事可以有更清晰地方式,就应该以较清晰地方式取代复杂的方式。“重构”可以把一些复杂东西分解为较简单的小块,但有时你就必须删除整个算法,代之以简单的算法。随着对问题有了更多理解,你往往会发现,在原先的做法之外,有更简单的解决方案,此时就需要改变原来的算法。如果你开始使用程序库,而其中提供的某些功能/特性与你自己的代码重复,那么你也需要改变原先的算法。

出处:http://blog.csdn.net/hguisu/article/details/7598066

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值