重新组织函数
1、ExtractMethod(提炼函数)
动机:你有一段代码可以被组织在一起并独立出来。
做法:将这段代码放进一个独立函数中,并让函数名称解释该函数的用途。
如下面的实例,提炼后的代码变得更清晰易读。
代码1:
voidprintOwing(doublepreviousAmount)){
Enumeratione=_orders.elemnets();
doubleoutstanding=previousAmount*1.2);
//pirntbanner
System.out.println("***************************");
System.out.println("******CustomerOwes******");
System.out.println("***************************");
//calculateoutstanding
while(e.hasMoreElements()){
Ordereach=(Order)e.nextElement();
outstanding+=each.getAmout();
}
//printdetails
System.out.print("name:"+_name);
System.out.print("amout:"+outstanding);
}
代码2:
voidprintOwing(doublepreviousAmount){
printBanner();
doubleoutstanding=getOutstanding(previousAmount*1.2);
printDetails(outstanding);
}
voidprintBanner(){
System.out.println("***************************");
System.out.println("******CustomerOwes******");
System.out.println("***************************");
}
doublegetOutstanding(doubleinitialValue){
doubleresult=initalValue;
Enumeratione=_orders.elemnets();
while(e.hasMoreElements()){
Ordereach=(Order)e.nextElement();
result+=each.getAmout();
}
returnresult;
}
voidprintDetails(doubleoutstanding){
System.out.print("name:"+_name);
System.out.print("amout:"+outstanding);
}
2、InlineMethod(内涵函数)
动机:一个函数的本体与名称同样清楚易懂。
做法:在函数调用点插入函数本体,然后移除该函数。
代码1:
intgetRating(){
return(moreThanFiveLateDeliveries())?2:1;
}
booleanmoreThanFiveLateDeliveries(){
return_numberOfLateDeliveries>5;
}
代码2:
intgetRating(){
return(_numberOfLateDeliveries>5)?2:1;
}
3、InlineTemp(内联临时变量)
动机:你有一个临时变量,只被一个简单表达式赋值一次,而它妨碍了其他重构手法。
做法:将所有对该变量的引用动作,替换为对它赋值的那个表达式自身。
备注:检查该变量是否真的被赋值一次,可以将该变量声明为final,然后编译。
代码1:
doublebasePrice=anOrder.basePrice();
return(basePrice>1000);
代码2:
return(anOrder.basePrice()>1000);
4、ReplaceTempwithQuery(以查询取代临时变量)
动机:临时变量的问题在于:他们是暂时的,而且只能在所属函数内使用。由于临时变量只在所属函数内可见,所以他们会驱使你写更长的函数,因为只有这样你才能访问到需要的临时变量。如果把临时变量替换为一个查询,那么同一个类中的所有函数都将可以获得这份信心。这将带给你极大帮助,使你能够为这个类编写更清晰的代码。
做法:将这个表达式提炼到一个独立函数中。将这个临时变量的所有引用点替换为对新函数的调用。此后,新函数就可被其他函数使用。
代码1:
doublebasePrice=_quantity*_itemPrice;
if(basePrice>1000)
reutrnbasePrice*0.95;
else
returnbasePrice*0.98;
代码2:
if(basePrice()>1000)
returnbasePrice()*0.95;
else
returnbasePrice()*0.98;
....
doublebasePrice(){
return_quantity*_itemPrice;
}
这个重构手法较为简单,如果临时变量比较多,还需要运用SplitTemporaryVariable(拆分临时变量)或SeparateQueryfromModifier(将查询函数和修改函数分离)使情况变得简单一些,然后再替换临时变量。如果你想替换的临时变量是用来收集结果的(例如循环中的累加值),就需要将某些程序逻辑(例如循环)复制到查询函数去。
代码1:
doublegetPrice(){
intbasePrice=_quantity*_itemPrice;
doublediscountFactor;
if(basePrice>1000)discountFactor=0.95;
elsediscountFactor=0.98;
returnbasePrice*discountFactor;
}
代码2:
doublegetPrice(){
returnbasePrice()*discountFactor();
}
privateintbasePrice(){
return_quantity*_itemPrice;
}
privatedoublediscountFactor(){
if(basePrice()>1000)return0.95;
elsereturn0.98;
}
5、IntroduceExplainingVariable(引入解释性变量)
动机:你有一个复杂的表达式。在条件逻辑中,IntroduceExplainingVariable特别有价值:你可以用这项重构将每个条件子句提炼出来,以一个良好命名的临时变量来解释对应条件子句的意义。使用这项重构的另一种情况是,在较长算法中,可以运用临时变量来解释,每一步运算的意义。
代码1:
if((platform.toupperCase().indexOf("MAC")>-1)&&
(browser.toUpperCase().indexOf("IE")>-1)&&
wasInitialized()&&resize>0){
//dosomething
}
代码2:
finalbooleanisMacOs=platform.toupperCase().indexOf("MAC")>-1;
finalbooleanisIEBrowser=browser.toUpperCase().indexOf("IE")>-1;
finalbooleanwasResized=resize>0;
if(isMacOs&&isIEBrowser&&wasResized){
//dosomething
}
类似的,我们也经常用ExtractMethod(提炼函数)来解释表达式用途,如下示例:
代码1:
doubleprice(){
//priceisbaseprice-quantitydiscount+shipping
return_quantity*_itemPrice-
Math.max(0,_quantity-500)*_itemPrice*0.05+
Math.min(_quantity*_itemPrice*0.1,100.0);
}
代码2:
doubleprice(){
returnbasePrice()-quantityDiscount()+shipping();
}
privatedoublequantityDiscount(){
returnMath.max(0,_quantity-500)*_itemPrice*0.05;
}
privatedoubleshipping(){
returnMath.min(basePrice()*0.1,100.0);
}
privatedoublebasePrice(){
return_quantity*_itemPrice;
}
IntroduceExplainingVariable(引入解释性变量)和ExtractMethod(提炼函数)比较:两者都可以达到解释表达式的效果,我觉得主要看临时变量是否被公用到,如果不是,则用引入解释性变量即可。
6、SplitTemporaryVariable(分解临时变量)
场景:你的程序有某个临时变量被赋值超过一次,它既不是循环变量,也不被用于手机计算结果。
分析:这种临时变量应该制备赋值一次。如果它们被赋值超过一次,就意味它们在函数中承担了一个以上的责任。如果临时变量承担多个责任,它就应该被替换(分解)为多个临时变量,每个变量只承担一个责任。同一个临时变量承担两件不同的事情,会令代码阅读者糊涂。
代码1:
doubletemp=2*(_height+_width);
System.out.println(temp);
temp=_height*_width;
System.out.println"temp);
代码2:
finaldoubleperimeter=2*(_height+_width);
System.out.println(perimeter);
finaldoublearea=_height*_width;
System.out.println(area);
7、RemoveAssignmentstoParameters(移除对参数的赋值)
场景:代码对一个参数进行赋值。
做法:以一个临时变量取代该参数的位置。
分析:从本质上说,参数是对象的引用,而对象的引用时按值传递的。因此我可以修改参数对象的内部状态(属性),但对参数对象重新赋值是没有意义。
代码1:
intdiscount(intinputVal,intquantity,intyearToDate){
if(inputVal>50)inputVal-=2;
}
代码2:
intdiscount(intinputVal,intquantity,intyearToDate){
intresult=inputVal;
if(inputVal>50)result-=2;
}
我还可以为参数加上关键词final,从而强制它遵循“不对参数赋值”这一惯例:
代码1:
intdiscount(intinputVal,intquantity,intyearToDate){
if(inputVal>50)inputVal-=2;
if(quantity>100)inputVal-=1;
if(yearToDate>10000)inputVal-=4;
}
代码2:
intdiscount(finalintinputVal,finalintquantity,finalintyearToDate){
intresult=inputVal;
if(inputVal>50)result-=2;
if(quantity>100)result-=1;
if(yearToDate>10000)result-=4;
}
不过我得承认,我不经常使用final来修饰参数,因为我发现,对于提高短函数的清晰度,这个方法并无太大帮助。我通常会在较长的函数中使用它,让它帮助我检查参数是否被做修改。
8、ReplaceMethodwithMethodObject(以函数对象取代函数)
动机:你有一个大型函数,其中对局部变量的使用使你无法采用ExtractMethod(提炼函数)。
做法:将这个函数放进一个单独对象中,如此一来局部变量就成了对象内的字段。然后你可以在同一个对象中将这些大型函数分解为多个小型函数。
9、SubsituteAlgorithm(替换算法)
动机:你需要把某个算法替换为另一个更清晰的算法。
做法:将函数本体替换为另一个算法。
代码1:
StringfoundPerson(String[]people){
for(inti=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";
}
}
return"";
}
代码2:
StringfoundPerson(String[]people){
Listcandidates=Arrays.asList(newString[]{"Don","John","Kent");
for(inti=0;i<people.length;i++){
if(candidates.contains(people[i])){
returnpeople[i];
}
}
return"";
}