重构——重新组织你的函数

1.    Extract Method

将这段代码放进一个独立函数中,并让函数名称解释该函数的用途。

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);

}

 

作法:

创造一个新函数,根据这个函数的意图来给它命名(以它[做什么]来命名,而不是以它[怎样做]命名)

ð 即使你想要提炼的代码非常简单,例如只是一条消息或一个函数调用,只要新函数的名称能够以更好方式昭示代码意图,你也应该提炼它。但如果你想不出一个更有意义的名称,就别动。

将提炼出的代码从源函数拷贝到新建的目标函数中。

仔细检查提炼出的代码,看看其中是否引用了[作用域限于源函数]的变量(包括局部变量和源函数参数)

检查是否有[仅用于被提炼码[的临时变量。如果有,在目标函数中将它们声明为临时变量。

检查被提炼码,看看是否有任何局部变量的值被它改变。如果一个临时变量值被修改了,看看是否可以将被提炼码处理为一个查询,并将结果赋值给相关变量。如果很难这样做,或如果被修改的变量不止一个,你就不能仅仅将这段代码原封不动地提炼出来。你可能需要先使用Split Temporary Variable,然后再尝试提炼。也可以使用Replace Temp with Query将临时变量消灭掉。

将被提炼码中需要读取的局部变量,当作参数传给目标函数。

处理完所有局部变量之后,进行编译。

在源函数中,将被提炼码替换为[为目标函数的调用]

ð 如果你将任何临时变量移到目标函数中,请检查它们原本的声明式是否在被提炼码的外围。如果是,现在你可以删除这些声明式了。

编译,测试。

 

2.    Inline Method

在函数调用点插入函数本体,然后移除该函数。

int getRating()

{

         return (moreThanFiveLateDeliveries()) ? 2 : 1;

}

Boolean moreThanFiveLateDeliveries()

{

         return _numberOfLateDeliveries > 5;

}

 

int getRating()

{

         return (_numberOfLateDeliveries > 5) ? 2 : 1;

}

 

作法:

检查函数,确定它不具多态性。

ð 如果subclass继承了这个函数,就不要将此函数inline化,因为subclass无法覆写一个根本不存在的函数。

找出这个函数的所有被调用点。

将这个函数的所有被调用点都替换为函数本体。

编译,测试。

删除该函数的定义。

 

3.    Inline Temp

将所有对该变量的引用动作,替换为对它赋值的那个表达式自身。

double basePrice = anOrder.basePrice();

return (basePrice > 1000);

 

return (anOrder.basePrice() > 1000);

 

作法:

如果这个临时变量并未被声明为final,那就将它声明为final,然后编译。

ð 这可以检查该临时变量是否真的只被赋值一次。

找到该临时变量的所有引用点,将它们替换为[为临时变量赋值]之语句中的等号右侧表达式。

每次修改后,编译并测试。

修改完所有引用点之后,删除该临时变量的声明式和赋值语句。

编译,测试。

 

4.    Replace Temp with Query

将这个表达式提炼到一个独立函数中。将这个临时变量的所有[被引用点]替换为[为新函数的调用]。新函数可被其它函数使用。

double basePrice = _quantity * _itemPrice;

if (basePrice > 1000)

         return basePrice * 0.95;

else

         return basePrice * 0.98;

 

if (basePrice() > 1000)

         return basePrice() * 0.95;

else

         return basePrice() * 0.98;

double basePrice()

{ return _quantity * _itemPrice; }

 

作法:

找出只被赋值一次的临时变量。

ð 如果某个临时变量被赋值超过一次,考虑使用Split Temporary Variable将它分割成多个变量。

将该临时变量声明为final

编译。

ð 这可确保该临时变量的确只被赋值一次。

[对该临时变量赋值]之语句的等号右侧部分提炼到一个独立函数中。

ð 首先将函数声明为private。日后你可能会发现有更多class需要使用它,彼时你可轻易放松对它的保护。

ð 确保提炼出来的函数无任何连带影响,也就是说该函数并不修改任何对象内容。如果它有连带影响,就对它进行Separate Query from Modifier

编译,测试。

在该临时变量身上实施Inline Temp

 

5.    Introduce Explaining Variable

将该复杂表达式的结果放进一个临时变量,以此变量名称来解释表达式用途。

if ( (platform.toUpperCase().indexOf(“MAC”) > -1) &&

   (browser.toUpperCase().indexOf(“IE”) > -1) &&

   wasInitialized() && resize > 0 )

{

         // do something

}

 

final boolean isMacOs = platform.toUpperCase().indexOf(“MAC”) > -1;

final boolean isIEBrowser = browser.toUpperCase().indexOf(“IE”) > -1;

final Boolean wasResized = resize > 0;

if ( isMacOs && isIEBrowser && wasInitialized() && wasResized)

{

         // do something

}

 

作法:

声明一个final临时变量,将待分解之复杂表达式中的一部分动作的运算结果赋值给它。

将表达式中的[运算结果]这一部分,替换为上述临时变量。

ð 如果被替换的这一部分在代码中重复出现,你可以每次一个,逐一替换,

编译,测试。

重复上述过程,处理表达式的其它部分。

 

6.    Split Temporary Variable

针对每次赋值,创造一个独立的、对应的临时变量。

double temp = 2 * (_height + _width);

System.out.println(temp);

temp = _height * _width;

System.out.println(temp);

 

final double perimeter = 2 * (_height + _width);

System.out.println(perimeter);

final double area = _height * _width;

System.out.println(area);

 

作法:

[待剖解]之临时变量的声明式及其第一次被赋值处,修改其名称。

ð 如果稍后之赋值语句是[i = i + 某表达式]形式,就意味这是全集用临时变量,那么就不要剖解它。集用临时变量的作用通常是累加、字符串接合、写入stream或者向群集添加元素。

将新的临时变量声明为final

以该临时变量之第二次赋值动作为界,修改此前对该临时变量的所有引用点,让它们引用新的临时变量。

在第二次赋值处,重新声明原先那个临时变量。

编译,测试。

逐次重复上述过程。每次都在声明处临时变量易名,并修改下次赋值之前的引用点。

 

7.    Remove Assignments to Parameters

以一个临时变量取代该参数的位置。

int discount (int inputVal, int quantity, int yearToDate)

{

         if (inputVal > 50) inputVal -= 2;

 

int discount(int inputVal, int quantity, int yearToDate)

{

         int result = inputVal;

         if (inputVal > 50) result -= 2;

 

作法:

建立一个临时变量,把待处理的参数值赋予它。

[对参数的赋值动作]为界,将其后所有对此参数的引用点,全局替换为[对此临时变量的引用动作]

修改赋值语句,使其改为对新建之临时变量赋值。

编译,测试。

ð 如果代码的语义是pass by reference,请在调用端检查调用后是否还使用了这个参数。也要检查有多少个pass by reference参数[被赋值后又被使用]。请尽量只以return方式返回一个值。如果需要返回的值不只一个,看看可否把需要返回的大堆数据变成单一对象,或干脆为每个返回值设计对应的一个独立函数。

 

8.    Replace Method with Method Object

将这个函数放进一个单独对象中。如此一来局部变量就成了对象内的值域。然后你可以在同一个对象中将这个大型函数分解为数个小型函数。

class Order...

 double price()

 {

         double primaryBasePrice;

         double secondaryBasePrice;

         double tertiaryBasePrice;

         // long computation;

         ...

}

 

 

作法:

建立一个新class,根据[待被处理之函数]的用途,为这个class命名。

在新class中建立一个final值域,用以保存原先大型函数所驻对象。我们将这个值域称为[源对象]。同时,针对原函数的每个临时变量和每个参数,在新class中建立一个个对应的值域保存之。

在新class中建立一个构造函数,接收源对象及原函数的所有参数作为参数。

在新class中建立一个compute()函数。

将原函数的代码拷贝到compute()函数中。如果需要调用源对象的任何函数,请以[源对象]值域调用。

编译。

将旧函数的函数本体替换为这样一条语句:[创建上述新class的一个新对象,而后调用其中的compute()函数]。

 

9.    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”;

         }

         return “”;

}

 

String foundPerson(string[] people)

{

         List candidates = Arrays.asList(new String[] {“Don”, “John”, “Kent”});

         for(int i=0; i < people.length; i++)

                   if(candidates.contains(people[i]))

                            return people[i];

         return “”;

}

 

作法:

准备好你的另一个算法,让它通过编译。

针对现有测试,执行上述的新算法。如果结果与原本结果相同,重构结束。

如果测试结果不同于原先,在测试和调试过程中。以旧算法为比较参照标准。

ð 对于每个test case,分别以新旧两种算法执行,并观察两者结果是否相同。这可以帮助你看到哪一个test case出现麻烦,以及出现了怎样的麻烦。

posted @ 2009-01-16 15:03  黄浩贤  阅读(208)  评论(0编辑  收藏  举报