《重构》读书笔记

《重构》学习笔记(04)-- 重新组织函数

2019-06-08  本文已影响0人  若隐爱读书

重构的手法中,很大的一部分就是对函数进行处理,使之更恰当的包装代码。一般公司的编程规范中,都会对函数长度进行限制(例如不能超过50行)。针对过长函数需要进行逻辑抽取,抽取过程中会使用以下的方法。

Extract Method(提取函数)

提取函数是最常用的手法之一,在大厂的编程规范中,都会对函数长度做限制,例如50行。函数提炼有三个好处:

  1. 如果函数颗粒度很小,那么函数被复用的机会就更大。
  2. 函数名称可以起到注释的作用,使程序读起来更加流畅。
  3. 如果函数都是细颗粒度,那么函数的覆写也会更容易一些。

提取函数难点和重点在于临时变量的处理。一般的做法如下:

Inline Method(内联函数)

如果一个函数的本体与名称同样清楚易懂,那么在函数调用点插入函数本体,然后移除该函数。例如以下代码段:

int getRating(){
    return (moreThanFiveLateDeliveries())?2:1;
}
boolean moreThanFiveLateDeliveries(){
    return _numberOfLateDeliveries > 5;
}

可以修改为:

int getRating(){
    return (_numberOfLateDeliveries > 5)?2:1;
}

另外一种需要使用Inline Method的情况是:你手上有一堆组织不甚合理的函数,你可以把它们都内联到一个大型函数中,再从中提炼组织出合理的小型函数。内联函数常用的做法:

Inline Temp(内联临时变量)

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

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

替换为

return (anOrder.basePrice() > 1000)

一般来说,只有临时变量妨碍了其他重构方法,你才需要进行内联化。内联临时变量的做法如下:

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

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

double basePrice = _quantity * _itemPrice;
if (basePrice > 1000)
    return basePrice * 0.95 ;
else 
    return basePrice * 0.98 ;

替换为:

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

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

临时变量的问题在于它是临时的,并且只能在所属函数内使用。如果将临时变量替换为一个查询,那么同一个类中所有函数都能够调用到。常用的做法:

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

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

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

重构为

const bool isMacOs     = platform.toUpperCase().indexOf("MAC") > -1;
const bool isIEBrowser = browser.toUpperCase().indexOf("IE") > -1;
const bool wasResized = resize > 0; 

if (isMacOs && isIEBrowser && wasInitialized() && wasResized())
{
    //do something
}

非常复杂的表达式可能难以阅读,这种情况下,临时变量可以帮助你将表达式分解为比较容易管理的形式。这种重构方法与Extrat Method类似,但作者更推荐使用后者。常用的做法为:

Split Temporary Variable(分解临时变量)

如果程序中有一个临时变量赋值超过一次,它既不是临时变量,也不用于收集计算结果。那么就应该针对每次赋值,创造一个独立、对应的临时变量。

double temp = 2 * (_height + _width);  
System.out.println(temp);  
temp = _height + _width;  
System.out.println(temp);  

重构为:

final double perimeter = 2 * (_height + _width);  
System.out.println(perimeter);  
final double area = _height + _width;  
System.out.println(area);  

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

在函数内对一个参数进行赋值,那么以一个临时变量取代该参数的位置。

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

重构为

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

由于传值和传址两种前提下,对参数赋值可能引起不同的结果。因此,在函数内对一参数进行赋值大大影响了函数的清晰度。一般的做法为:

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

如果你有一个大型函数,对其中的局部变量无法使用Extract Method。那么将这个函数放进一个单独对象中,局部变量转换为对象内的字段。那么可以在同一对象中将这个大型函数分解为多个小函数。
做法为:

Substitute Algorithm(替换算法)

string FoundPerson(string[] people){
    foreach (var person in people){
        if (person == "Don"){
            return "Don";
        }if (person == "John"){
            return "John";
        }
    }
    return string.Empty;
}

重构为

String foundPerson(String[] people) {
    List candidates = Arrays.asList(new String[],{"Don", "John", "Kent"});
    for(int i = 0; i < people.length; i++)
       if(candidates.contains(people[i]))
          return people[i];
    return "";
}

解决问题的方法有很多种,采用更加清晰简洁的算法取代复杂的实现方式,一般的做法为:

上一篇 下一篇

猜你喜欢

热点阅读