第六章 重新组织函数
一、Extrat Method(提炼函数)
定义:将这段代码放进一个独立函数中,并让函数名称解释该函数的用途。
自我理解:就是将一个复杂的函数或者过长的函数分解成独立的小函数。
例子:
void printOwing(){ Enumeration e =_orders.elements(); double outstanding = 0.0; //print banner System.out.println("*********************"); System.out.println("******Banner*********"); System.out.println("*********************"); //calucate outstanding while (e.hasMoreElements()) { Order each = (Order) e.nextElement(); outstanding += each.getAmount(); } //print details System.out.println("name:" + _name); System.out.println("amount" + outstanding); }
void printOwing(){ printBanner(); double outstanding = calucateOutstanding(12); printDetails(); } void printDetails(double ountstanding){ System.out.println("name:" + _name); System.out.println("amount" + outstanding); } void calucateOutstanding(double initValue){ double result = initValue; Enumeration e =_orders.elements(); while (e.hasMoreElements()) { Order each = (Order) e.nextElement(); result += each.getAmount(); } return relust; } void printBanner(){ //print banner System.out.println("*********************"); System.out.println("******Banner*********"); System.out.println("*********************"); }
当看见一个过长的函数或者一段需要注释才能让人理解的用途的代码,我就会将这段代码放进一个独立的函数中。
优点:
1.每个函数的粒度都很小,那么函数的复用机会会很大。
2.这会使高层函数读起来就想一系列注释。
3.如果函数都是细粒度,那么函数的覆写也会更容易一些。
注意:
1.创造一个函数,根据这个函数的意图来对它进行命名(以它“做什么”来命名,而不是以它“怎么做”命名)。
2.注意函数的临时变量的变化。
3.函数名称应该清晰,能够清楚表达该函数具体是做什么。
二、Inline Method(内联函数)
定义:一个函数本体与名称清楚易懂,在函数调用点插入函数本体,然后移除该函数。
自我理解:如果啊函数内部的代码和函数名称同样清晰易读,那就去掉这个函数,虽然间接性可能带来帮助,但是非必要的间接性总是让人不舒服。通常函数主体为一行代码。
int getRating(){
return (moreThanFiveLateDeliveries())?2:1;
}
boolean moreThanFiveLateDeliveries(){
return _numberOflateDeliveries>5;
}
int getRating(){
return (_numberOflateDeliveries>5)?2:1;
}
注意:
1.如果使用太多的间接层,使得系统中所有的函数似乎只是对另一个函数的简单委托,会被这些委托动作搞的晕头转向,那么请使用本重构。
三、Inline Temp(内联变量)
定义:有一个临时变量,只被一个简单表达式赋值一次,而它妨碍了其他重构的手法。将所有对该变量的引用动作,替换为对它赋值的那个表达式。
自我理解:一般来说,这个临时变量不会有任何的危害,但是如果妨害了其他的重构方法,例如抽取函数,那么就将它内联。
double basePrice = anOrder.basePrice(); return (basePrice > 1000);
return (anOrder.basePrice() > 1000);
注意:
1.如果临时变量过多地方被引用,可以使用final进行确定这个变量是否只被赋值一次。
四、Replace Temp with Query(以查询取代临时变量)
定义:你的程序以一个临时 变量保存某一表达式的运算结构。将这个表达式提炼到一个独立的函数中。将这个临时变量的所有引用点替换为新函数的调用。此后,新函数就可以被其他函数使用。
自我理解:这个重构一般是抽取函数之前必不可少的一步,局部变量会使代码难以被提炼,所以你应该尽可能把他们替换为查询式。
double getPrice(){ int basePrice = _quatity*_itemPrice; double discountFactor; if(basePrice>1000) discountFactor = 0.95; else discountFactor = 0.98; return basePrice * discountFactor; }
double getPrice(){ return getBasePrice() * getDisCountFactor(); } int getBasePrice(){ return _quatity*_itemPrice; } double getDisCountFactor(){ if(getBasePrice()>1000) return 0.95; else return 0.98; }
注意:
1.我们常常使用临时变量保存循环中累加信息,在这种情况下,我们经常将这个循环都可以被提炼一个独立函数。如果一个循环累加几个值,建议对每个值见一个函数进行累加,性能先暂时不考虑。
五、Introduce Explaining Variable(引入解释性变量)
定义:有一个复杂的表达式,将该复杂的表达式(或者其中一部分)的结果放进一个临时变量,以此变量名称来解释表达式用途。
自我理解:表达式有时候可能非常复杂而难以阅读,临时变量可以帮主你将表达式分解为比较容易管理和理解的形式。
double price(){ //price is base price - quantity discount + shipping return _quantity * _itemPrice -Math.max(0,_quantity-500)*_itemPrice*0.05 +Math.min(_quantity*_itemPrice*0.1,100); }
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); return basePrice - quantityDiscount + shipping; }
也可以提供抽取方法的重构:
double price(){ return getBasePrice() - getQuantityDiscount() + getShipping(); } double getBasePrice(){ return _quantity * _itemPrice; } double getQuantityDiscount(){ return Math.max(0,_quantity-500)*_itemPrice*0.05; } double getShipping(){ return Math.min(getBasePrice()*0.1,100); }
注意:
1.这个重构好像与"以查询取代临时变量"重构冲突,这两个方法可以同时使用,可以对程序进行更好的理解,不过个人推荐抽取方法此重构,因为临时变量拥有很大的局限性,只能在方法内部使用,函数在对象的生命周期都可以进行使用,也可以提供外部使用。
六、Split Temporary Variable(分解临时变量)
定义:程序有某个临时变量被赋值超过一次,它既不是循环变量,也不是被用于收集计算结果。针对每次赋值,创造一个独立、对应的临时变量
自我理解:如果临时变量被赋值超过一次,就意味在函数中承担了一个以上的责任,如果临时变量承担多个责任,它就应该被分节成多个临时变量,每个变量只承担一个责任。同一个临时变量承担两件不同的事情。
double getDistanceTravelled(int time){ double result; double acc = _primaryForce / _mass; int primaryTime = Math.min(time,_delay); result = 0.5 * acc * primaryTime *primaryTime; int secondaryTime = time - _delay; if(secondaryTime > 0){ double primaryVel = acc * _delay; acc = (_primaryForce + _secondaryForce) / _mass; result += primaryVel *_secondaryForce + 0.5 * acc *secondaryTime * secondaryTime; } return result; }
double getDistanceTravelled(int time){ return getPrimaryDistanceTravelled(time) + getSecondDistanceTravelled(time); } double getPrimaryDistanceTravelled(int time){ double acc = _primaryForce / _mass; int primaryTime = Math.min(time,_delay); return 0.5 * acc * primaryTime *primaryTime; } double getSecondDistanceTravelled(int time){ double firstAcc = _primaryForce / _mass; int secondaryTime = time - _delay; if(secondaryTime > 0){ double primaryVel = firstAcc * _delay; double secondAcc = (_primaryForce + _secondaryForce) / _mass; return primaryVel *_secondaryForce + 0.5 * secondAcc *secondaryTime * secondaryTime; } return 0d; }注意:
1.结果集变量和循环变量可以进行重新赋值。
七、Remove Assignments to Parameters(移除对参数的赋值)
定义:代码对一个参数进行 赋值。以一个临时变量取代该参数的位置。
自我理解:“对参数赋值”意味改变参数,会造成职责不明。
int discount(int inputVal,int quantity, int yearToDate)(){ if(inputVal > 50) inputVal -= 2; if(quantity >100) inputVal -= 1; if(yearToDate > 100000) inputVal -=4; return inputVal; }
int discount(int inputVal,int quantity, int yearToDate)(){ int result = inputVal; if(inputVal > 50) result -= 2; if(quantity >100) result -= 1; if(yearToDate > 100000) result -=4; return result; }
注意:
1.如果是对象传值,因此可以修改参数对象的内部状态,但对参数对象重新赋值是没有意义。
八、Replace Method with Method OBject(以函数对象取代函数)
定义:有一个大型的函数,其中对局部变量的使用使你无法采用Extract Method。将这个函数放进 单独对象中,如此一来局部变量就成了对象内的字段。然后可以在同一个对象中将这个大象函数分解成为多个小型函数。
自我理解:如果一个函数之中局部变量泛滥成灾,那么分解 必定十分困难。在建立方法对象时,需要针对原函数的每个临时变量和每个参数,在新类中建立了一个对应的字段进行保护。
//Class Accout int gamma(int inputVal, int quantity,int yearToDate){ int importtantValue1 = (inputVal * quantity) + delta(); int importtantValue2 = (inputVal * yearToDate) + 100; if((yearToDate - importtantValue1) > 100) importtantValue2 -= 20; int importtantValue3 = importtantValue2 * 7; //and so on. return importtantValue3 - 2 * importtantValue1; }
class Gamma{ private final Account _account; private int inputVal; private int quantity; private int yearToDate; private int importtantValue1; private int importtantValue2; private int importtantValue3; public Gamma(Accout source,int inputValArg,int quantityArg,input yearToDateArg){ _accout = source; inputVal = inputValArg; quantity = quantityArg; yearToDate = yearToDateArg; } int compute(){ importtantValue1 = (inputVal * quantity) + _account.delta(); importtantValue2 = (inputVal * yearToDate) + 100; importantThing(); importtantValue3 = importtantValue2 * 7; //and so on return importtantValue3 - 2 * importtantValue1; } void importtantThing(){ if((yearToDate-importtantValue1) > 100) importtantValue2 -= 20; } }注意:
1.由于我们将局部变量我们就可以顺利将大函数分解一个一个小函数,也方便我们去分析这个函数具体的作用与流程,这个重构特别对大型的重构方法有特别效果。
2.要将本对象当一个构造参数传入方法函数。
九、Substitute Algorithm(替换算法)
定义:你想要某个算法替换为另一个清晰的算法。将函数本体替换为另一个算法。
自我理解:有些算法是陈旧复杂的,例如循环的遍历等等。
String foundPerson(String[] people){ for (int i = 0; i < people.length; i++) { if (people[i].equals("Don")) { return "Don"; } if (people[i].equals("John")) { return "John"; } if (people[i].equals("Kent")) { return "Kent"; } } }
String foundPerson(String[] people){ List cadidates = Arrays.asList(new String[]{"Don","John","Kent"}); for (int i = 0; i < people.length; i++) { if (cadidates.contains(people[i])) { return people[i]; } } return ""; }