您的位置:首页 > 其它

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

2015-01-24 21:05 267 查看


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

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

作法:

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

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

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

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

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

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

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

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

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

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

o 编译,测试。


2. Inline Method

在函数调用点插入函数本体,然后移除该函数。
int getRating()
{
return (moreThanFiveLateDeliveries()) ? 2 : 1;
}
Boolean moreThanFiveLateDeliveries()
{
return _numberOfLateDeliveries > 5;
}

int getRating()
{
return (_numberOfLateDeliveries > 5) ? 2 : 1;
}

作法:

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

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

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

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

o 编译,测试。

o 删除该函数的定义。


3. Inline Temp

将所有对该变量的引用动作,替换为对它赋值的那个表达式自身。
double basePrice = anOrder.basePrice();
return (basePrice > 1000);

return (anOrder.basePrice() > 1000);

作法:

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

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

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

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

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

o 编译,测试。


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

作法:

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

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

o 将该临时变量声明为final。

o 编译。

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

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

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

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

o 编译,测试。

o 在该临时变量身上实施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
}

作法:

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

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

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

o 编译,测试。

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


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

作法:

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

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

o 将新的临时变量声明为final。

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

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

o 编译,测试。

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


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;

作法:

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

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

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

o 编译,测试。

ð 如果代码的语义是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;
...
}




作法:

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

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

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

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

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

o 编译。

o 将旧函数的函数本体替换为这样一条语句:[创建上述新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 “”;
}

作法:

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

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

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

ð 对于每个test case,分别以新旧两种算法执行,并观察两者结果是否相同。这可以帮助你看到哪一个test case出现麻烦,以及出现了怎样的麻烦。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: