第6章重新组织函数重构在多数情况下是对函数进行整理,过长函数是遇到的最多问题。它们包含太多信息,这些信息又被函数错综复杂的逻辑掩盖,不易鉴别。常用的解决方法就是提炼函数,把一段代码从原先函数中提取出来,放进单独函数。6.1提炼函数voidprintOwing(doubleamount){printBanner();//printdetailsSystem.out.println(name:+_name);System.out.println(amount+amount);}voidprintOwing(doubleamount){printBanner();printDetails(amount);}voidprintDetails(doubleamount){System.out.println(name:+_name);System.out.println(amount+amount);}将这段代码放进一个独立函数中,并让函数名称解释该函数的用途。优点:•增加函数彼此间复用的机会•使高层函数码读起来象一系列注释方法:•创造新函数,根据这个函数的用途来给它命名。即使要提炼的代码非常简单,例如只是一条消息或一个函数调用,只要新函数的名称能够以更好方式表示代码意图,应该提炼它。但如果想不出更有意义的名称,就别动。•将提炼出的代码从源函数拷贝到新建的目标函数中。•仔细检查提炼出的代码,看看其是否引用了作用域限于源函数的变量(包括局部变量和源函数参数)。•检查是否有仅用于被提炼部分的临时变量。如果有,在目标函数中将它们声明为临时变量。5.检查被提炼码,看看是否有任何局部变量的值被改变。如果临时变量值被修改,看是否可以将被提炼码处理为一个查询,并将结果赋值给相关变量。6.将被提炼代码中需要读取的局部变量,当做参数传给目标函数。7.处理完所有局部变量之后,进行编译。8.在源函数中,将被提炼码替换为对目标函数的调用。如果将任何临时变量移到目标函数中,检查它们原本的声明式是否在被提炼码的外围。如果是,删除这些声明式。9.编译,测试。范例:无局部变量voidprintOwing(){Enumeratione=_orders.elements();doubleoutstanding=0.0;//printbannerSystem.out.println(**************************);System.out.println(*****CustomerOwes******);System.out.println(**************************);//calculateoutstandingwhile(e.hasMoreElements()){Ordereach=(Order)e.nextElement();outstanding+=each.getAmount();}//printdetailsSystem.out.println(name:+_name);System.out.println(amount+outstanding);}提炼出“打印banner”的代码。只需要剪切、粘贴、再插入一个函数调用动作:voidprintOwing(){Enumeratione=_orders.elements();doubleoutstanding=0.0;printBanner();//calculateoutstandingwhile(e.hasMoreElements()){Ordereach=(Order)e.nextElement();outstanding+=each.getAmount();}//printdetailsSystem.out.println(name:+_name);System.out.println(amount+outstanding);}voidprintBanner(){//printbannerSystem.out.println(**************************);System.out.println(*****CustomerOwes******);System.out.println(**************************);}•有局部变量包括传进源函数的参数和源函数所声明的临时变量。•如果被提炼码只是读取这些变量的值,并不修改它们,那么可简单地将它们当做参数传给目标函数。voidprintOwing(){Enumeratione=_orders.elements();doubleoutstanding=0.0;printBanner();//calculateoutstandingwhile(e.hasMoreElements()){Ordereach=(Order)e.nextElement();outstanding+=each.getAmount();}//printdetailsSystem.out.println(name:+_name);System.out.println(amount+outstanding);}可以将“打印详细信息”这一部分提炼为带参数的函数:voidprintOwing(){Enumeratione=_orders.elements();doubleoutstanding=0.0;printBanner();//calculateoutstandingwhile(e.hasMoreElements()){Ordereach=(Order)e.nextElement();outstanding+=each.getAmount();}printDetails(outstanding);}voidprintDetails(doubleoutstanding){System.out.println(name:+_name);System.out.println(amount+outstanding);}如果局部变量是个对象,而被提炼码调用了会对该对象造成修改的函数,也可以同样操作,只需将这个对象作为参数传递给目标函数即可。•对局部变量再赋值1.这个变量只在被提炼码区间使用。可以将这个临时变量的声明式移到被提炼码中,然后一起提出去。2.被提炼码之外的代码也使用了这个变量。这又分为两种情况:如果这个变量在被提炼码之后未再被使用,只需直接在目标函数中修改它;如果被提炼码之后的代码还使用了这个变量,就需要让目标函数返回该变量改变后的值。这里讨论临时变量的问题。被赋值的临时变量分两种情况:voidprintOwing(){Enumeratione=_orders.elements();doubleoutstanding=0.0;printBanner();//calculateoutstandingwhile(e.hasMoreElements()){Ordereach=(Order)e.nextElement();outstanding+=each.getAmount();}printDetails(outstanding);}voidprintOwing(){printBanner();doubleoutstanding=getOutstanding();printDetails(outstanding);}doublegetOutstanding(){Enumeratione=_orders.elements();doubleoutstanding=0.0;while(e.hasMoreElements()){Ordereach=(Order)e.nextElement();outstanding+=each.getAmount();}returnoutstanding;}Enumeration变量e只在被提炼码中用到,所以将它整个搬到新函数中。double变量outstanding在被提炼码内外都被用到,必须让提炼出来的新函数返回它。编译测试完成后,把回传值改名,命名原则:doublegetOutstanding(){Enumeratione=_orders.elements();doubleresult=0.0;while(e.hasMoreElements()){Ordereach=(Order)e.nextElement();result=each.getAmount();}returnresult;}outstanding变量只是很简单地被初始化为一个明确初值,所以可以只在新函数中对它初始化。如果代码还对这个变量做了其他处理,就必须将它的值作为参数传给目标函数。voidprintOwing(doublepreviousAmount){Enumeratione=_orders.elements();doubleoutstanding=previousAmount*1.2;printBanner();//calculateoutstandingwhile(e.hasMoreElements()){Ordereach=(Order)e.nextElement();outstanding+=each.getAmount();}printDetails(outstanding);}voidprintOwing(doublepreviousAmount){doubleoutstanding=previousAmount*1.2;printBanner();outstanding=getOutstanding(outstanding);printDetails(outstanding);}doublegetOutstanding(doubleinitialValue){doubleresult=initialValue;Enumeratione=_orders.elements();while(e.hasMoreElements()){Ordereach=(Order)e.nextElement();result+=each.getAmount();}returnresult;}在将变量outstanding的初始化过程整理如下:voidprintOwing(doublepreviousAmount){printBanner();doubleoutstanding=getOutstanding(previousAmount*1.2);printDetails(outstanding);}6.2将函数内联化在函数调用点插入函数本体,然后移除该函数。intgetRating(){return(moreThanFiveLateDeliveries())?2:1;}booleanmoreThanFiveLateDeliveries(){return_numberOfLateDeliveries5;}intgetRating(){return(_numberOfLateDeliveries5)?2:1;}•以简短的函数表现动作意图,会使代码更清晰易读。但有时候会遇到某些函数,其内部代码和函数名称同样清晰易读。这时就应该去掉这个函数,直接使用其中的代码。间接性可能带来帮助,但不必要的间接性总是让人不舒服。•有一群组织不合理的函数,可以将它们都内联到一个大型函数中,再从中提炼出组织合理的小型函数。可以把所要调用对象的函数内容都放到函数对象中。比起既要移动一个函数,又要移动它所调用的其他所有函数,将大型函数作为单一整体来移动要简单得多。•如果别人使用了太多间接层,使得系统所有函数都似乎只是对另一个函数的简单委託,造成在这些委託动作之间晕头转向,那么通常都采用内联方法。方法:1.检查函数,确定它不具多态性。如果子类继承了这个函数,就不要将此函数内联化,因为子类无法覆写一个根本不存在的函数。2.找出这个函数的所有被调用点。3.将这个函数的所有被调用点都替换为函数本体(代码)。4.编译,测试。5.删除该函数的