1. 提炼函数(Extract Method)
1.1 动机
(1)函数的粒度小,被复用的机会就会更大。而且细粒度的函数覆写也会更容易。
(2)简短而命名良好的函数,会使高层函数读起来就像一系列注释
1.2 做法
(1)创建一个新函数,并根据函数的意图命名(以它“做什么”来命名,而不是以它“怎么做”命名)
(2)将提炼出的代码从源函数复制到新建的目标函数中。
(3)仔细检查提炼出的代码,看看其中是否引用了“作用域限于源函数”的变量(如即是还是使用了源函数中的局部变量或参数)
(4)检查是否有“仅用于被提炼代码段”的临时变量。如果有,在目标函数中将它们声明为临时变量。
(5)检查被提炼代码段,看看是否有任何局部变量的值被它改变。如果有一个临时变量的值被修改了,看看是否可以被将提炼代码炼处理为一个查询,并将结果赋值给相关变量。如果被修改的变量不止一个,可以先用Split Temporary Variable,然后再尝试提炼。也可以使用Replace Temp with Query将临时变量消灭掉。
(6)将被提炼代码段中需要读取的局部变量,当作参数传给目标函数。
(7)在源函数中,将被提炼代码段替换为对目标函数的调用。
1.3 范例
//重构前(Java)
//源函数 void printOwing(double previousAmount) { Enumeration e = _orders.elements(); //_orders是类的成员变量 double outstanding = previousAmount * 1.2; //print banner(无局部变量) System.out.println("*****************************"); System.out.println("********Customer Owes********"); System.out.println("*****************************"); //calculate outstanding while(e.hasMoreElements()) { Order each = (Order) e.nextElement(); outstanding += each.getAmount(); } //print details System.out.println("name:" + _name);//_name为成员函数 System.out.println("amount" + outstanding);//outstanding为局部变量 }
//重构后
//经Extract Method 提炼后的函数 void printOwing(double previousAmount) { printBanner(); double outstanding = getOutstanding(previousAmount * 1.2); printDetail(outstanding); } //无局部变量,直接提取(函数名以意图命令) void printBanner() { //print banner System.out.println("*****************************"); System.out.println("********Customer Owes********"); System.out.println("*****************************"); } //有局部变量outstanding,将其作为参数传递给目标函数(函数名以意图命令) void printDetails(double outstanding) { System.out.println("name:" + _name); System.out.println("amount" + outstanding); } //对局部变量再赋值,分为两种情况:(函数名以意图命令) //1.该变量只在被提炼代码中使用时,可直接将该变量的声明移到目标函数中 //2.该变量在被提炼代码之个也使用了这个变量,又分两种情况 // A.变量在被提炼代码之后未再被使用,只需直接在目标函数中修改它便可 // B.变量在被提炼代码之后还被使用,可让目标函数返回修改后的变量(本例属这情况) double getOutStanding(double initValue) { //源函数中变量e只在被提炼代码段中用到,可搬到新函数中定义 Enumeration e = _orders.elements(); //源函数中outstanding在被提炼代码段内外都被使用,可让目标函数返回该值 double result = initValue; //outstanding //calculate outstanding while(e.hasMoreElements()) { Order each = (Order) e.nextElement(); result += each.getAmount(); } return result; }
1.4 思考
(1)Extract Method最大的困难是处理局部变量,而临时变量是其中之一。
(2)可以用Replace Temp with Query去掉所有可去掉的临时变量。
(3)如果很多地方使用了某个临时变量,可用Split Temporary Variable将它变得比较容易替换。
(4)也可以用Replace Method with Method Object引入一个新类也处理临时变量。
(5)参数带来的问题比较容易处理。但如果在函数内赋值给参数了,可以用Remove Assignments to Parameters来处理。