-
重构方式
- 将下面的代码放进一个独立函数中,并让函数名称解释该函数的用途
void printOwing(double amount){ printBanner(); //print details System.out.println("name:"+_name); System.out.println("amount"+amount); } |
void printOwing(double amount){ printBanner(); printDetails(amount); }
void printDetails(double amount){ System.out.println("name:"+_name); System.out.println("amount"+ amount); } |
-
动机
-
当出现一个过长的函数或者一段需要注释才能让人理解用途的代码,都可以将这段代码放进一个独立函数中;
- 函数粒度很小,函数被复用的机会就更大;
- 可以使高层函数读起来像一系列注释;
- 如果函数都是细粒度,那么函数的覆写也会更容易些;
-
-
做法
- 创建一个新函数,根据这个函数的意图来对它命名(以它"做什么"来命名,而不是以它"怎样做"命名);
- 将提炼出的代码从源函数复制到新建的目标函数中;
- 仔细检查提炼出的代码,看看其中是否引用了"作用域限于源函数"的变量(包括局部变量和源函数参数);
- 检查是否有"仅用于被提炼代码段"的临时变量。如果有,在目标函数中将它们声明为临时变量;
- 检查被提炼代码段,看看是否有任何局部变量的值被它改变。如果一个临时变量被修改了,看看是否可以将被提炼代码段处理为一个查询,并将结果赋值给相关变量。如果很难这样做,或如果被修改的变量不止一个,你就不能仅仅将这段代码原封不动地体谅出来。你可能需要先"分解临时变量",然后再尝试提炼。也可以用"以查询替代临时变量"将临时变量消灭掉;
- 将被提炼代码段中需要读取的局部变量,当作参数传给目标函数;
- 处理完所有局部变量之后,进行编译;
- 在源函数中,将被提炼代码段替换为对目标函数的调用;
-
范例:无局部变量
- 问题函数:
void printOwing(){ Enumeration e = _orders.elements(); double outstanding = 0.0; //print banner System.out.println("*******************"); System.out.println("***Customer Owes***"); System.out.println("*******************"); //calchlate 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(){ Enumeration e = _orders.elements(); double outstanding = 0.0; printBanner(); //calchlate 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 printBanner() { //print banner System.out.println("*******************"); System.out.println("***Customer Owes***"); System.out.println("*******************"); } |
-
范例:有局部变量
- 第一种情况:被提炼代码段只是读取这些变量的值,并不修改它们。这种情况下,可以简单地将它们当作参数传给目标函数。这种方式可以处理多个局部变量或者该变量是个对象:
void printOwing(){ Enumeration e = _orders.elements(); double outstanding = 0.0; printBanner(); //calchlate 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(){ Enumeration e = _orders.elements(); double outstanding = 0.0; printBanner(); //calchlate outstanding while(e.hasMoreElements()){ Order each = (Order)e.nextElement(); outstanding += each.getAmount(); } printDetails(outstanding); }
void printDetails(double outstanding) { //print details System.out.println("name:" + _name); System.out.println("amount" + outstanding); } |
-
第二种情况(对局部变量再赋值):此情况只讨论被赋值的临时变量;
- 示例代码:
void printOwing(){ Enumeration e = _orders.elements(); double outstanding = 0.0; printBanner(); //calchlate outstanding while(e.hasMoreElements()){ Order each = (Order)e.nextElement(); outstanding += each.getAmount(); } printDetails(outstanding); } |
- 问题1:如果源函数的参数被赋值,请马上"移除对参数的赋值"!
-
被赋值的临时变量分两种情况:
- 该变量只在被提炼代码段中使用。可以将这个临时变量的声明移到被提炼代码段中,然后一起提炼出去;
-
被提炼代码段之外的代码也使用了这个变量,分两种情况:
- 如果这个变量在被提炼代码段之后,未再被使用,只需直接在目标函数中修改它就可以了;
- 如果被提炼代码段之后的代码还使用了这个变量,需要让目标函数返回该变量改变后的值;
- 了解以上情况后,现在尝试将"计算"代码提炼出来:
void printOwing(){ printBanner(); double outstanding = getOutstanding(); printDetails(outstanding); } double getOutstanding() { Enumeration e = _orders.elements(); double outstanding = 0.0; while(e.hasMoreElements()){ Order each = (Order)e.nextElement(); outstanding += each.getAmount(); } return outstanding; } |
- Enumeration变量e只在被提炼代码段中用到,所以可以将它整个搬到新函数中。double变量outstanding在被提炼代码段内外都被用到,所以必须让提炼出来的新函数返回它。编译通过后,把回传值改名:
double getOutstanding() { Enumeration e = _orders.elements(); double result = 0.0; while(e.hasMoreElements()){ Order each = (Order)e.nextElement(); result += each.getAmount(); } return result; } |
- outstanding变量只是很单纯地被初始化为一个明确初值,所以可以只在新函数中对它初始化。如果代码还对这个变量做了其他处理,就必须将它的值作为参数传给目标函数。对于这种变化,最初的代码可能是这样:
void printOwing(double previousAmount){ Enumeration e = _orders.elements(); double outstanding = previousAmount * 1.2; printBanner(); while(e.hasMoreElements()){ Order each = (Order)e.nextElement(); outstanding += each.getAmount(); } printDetails(outstanding); } |
- 提炼代码:
void printOwing(double previousAmount){ double outstanding = previousAmount * 1.2; printBanner(); outstanding = getOutstanding(outstanding); printDetails(outstanding); } double getOutstanding(double initialValue) { double result = 0.0; Enumeration e = _orders.elements(); while(e.hasMoreElements()){ Order each = (Order)e.nextElement(); result += each.getAmount(); } return result; } |
- 通过编译后,将变量outstanding的初始化过程进行整理:
void printOwing(double previousAmount){ printBanner(); double outstanding = getOutstanding(previousAmount * 1.2); printDetails(outstanding); } |
-
问题:需要返回的变量不止一个,该怎么办?
- 挑选另一块代码来提炼。尽可能让函数都只返回一个值,用多个函数来返回多个值;
-
写在最后
- 临时变量往往为数众多。这种情况下,可以尝试"以查询取代临时变量"的方式减少临时变量。如果这样做无效,可以尝试"以函数对象取代函数";