您的位置:首页 > 其它

重构方法之重新组织函数

2015-05-08 18:12 429 查看
一、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);

}

动机

1.每个函数粒度都很小,那么函数被复用的机会就更大; 

2.其次,这会使高层函数读起来就像一系列注释;

3.再次,如果函数都是细粒度,那么函数覆写也更容易。

做法

1.创建一个新函数,根据这个函数的意图来对它命名(以它做什么来命名)

2.将提炼的代码复制到新建的目标函数中。

3.检查提炼的代码,看看其中是否引用了“”作用域限于源函数“的变量(包括局部变量和原函数参数)

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

5.检查被提炼的代码段,看看是否有任何局部变量的值被改变。如果有一个临时变量被修改,就可以Replace Temp with Query

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

7.在源函数中,将被提炼地 代码段替换为对目标函数的调用

二、Inline Method(内敛函数)

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

示例

int getRating(){

return moreThanFiveLateDeliveries() ? 2:1;

}

boolean moreThanFiveLateDeliveries(){

return  _numberOfLateDeliveries > 5;

}

call fun

int getRating(){

return (_numberOfLateDeliveries > 5) ? 2:1;

}

动机

1.以简短的函数表现动作意图,这样会使代码更清晰易读取。

做法

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

2.找出此函数的所有被调用点

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

三、Inline Temp(内连临时变量)

有一个临时变量,只被一个简单表达式赋值一次,而它妨碍了其他重构守法

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

示例

double basePrice = anOther.basePrice();

return (basePrice > 1000)

return (anOther.basePrice() > 1000);

动机

Inline Temp 多半是作为Replace Temp with Query 的一部分使用的。唯一单独使用Inline Tenp 的情况是,你发现某个临时变量被赋予某个函数的返回值,把这个返回值替换成对应函数。

做法

1.检查临时变量赋值的语句,确保等号右边的表达式没有副作用。

2.如果临时变量没有被声明为final,那么就将它声明为final,然后编译。(确保该临时变量只被赋值一次)

3.把用到该临时变量的地方替换为“为临时变量赋值“的表达式。

四、Replace Temp with Query(以查询取代临变量)

你的程序以一个临时变量保存某一个表达式的运算结果。

将这个表达式提炼到一个独立函数中,将这个临时变量的所有引用都替换成对新函数的调用。

示例

重构前

double basePrice = _quantity * _temPrice

if(basePrice > 1000)

return basePrice * 0.95;

else

reurn basePrice * 0.95

重构后

if(basePrice() > 1000)

return basePrice() * 0.95;

else

reurn basePrice() * 0.95

// 提炼的函数

double  basePrice(){

return _quantity * _temPrice ;

}

动机

如果能将临时变量替换为一个查询,那么同一个类的所有函数都将可以获得这份信息。这将使代码逻辑更加清晰

做法

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

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

2.确保某个变量只被赋值一次,可以将该变量声明为final

3.将临时变量等号右侧的部分提炼到一个独立函数中。

(1)首先将函数声明为private,日后可能会发现有更多类需要使用它,那时候在放松它的权限

(2)确保提炼出来的函数无任何副作用,也就是说函数并不修改任何对象内容,如果它有副作用,就Separate Query from Modifier
fb9a

注意:这种替换手法有可能会造成性能问题,我们需要注意。

五、Introduce Explaining Variable(引用解释性变量)

有一个复杂的表达式,将该复杂的表达式(或其中一部分)的结果放进一个临时变量,以此变量名称来解释表达式的用途

示例

重构前

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

(browser.toUpperCase().indexOf("IE") > -1 ) &&

(wasInitialized() && resized > 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临时变量,将待分解的复杂表达式中的一部分动作的运算结果赋值给它,把表达式替换成变量。

六、Split Temporary Variable(分解临时变量)

程序中有时候一个临时变量被赋值超过一次,它既不是循环变量也不是用于收集计算结果的;

此时为了清晰的逻辑以及重构的必要,针对每次赋值,创建一个独立、对应的临时变量

示例

重构前

double temp = 2 * (_height + _weight);

System.out.println(temp);

temp = _heigth * _weight;

System.out.println(temp);

重构后

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

System.out.println(perimeter);

final double area = _heigth * _weight;

System.out.println(area);

动机

临时变量有各种不同的用途,其中有些用途会很自然地被多次赋值。其中,“循环变量”和“结果收集”就两个典型的例子。除了这两种情况,有很多情况下,临时变量被赋值不止一次,这就意味着,变量承担多个责任,那么这个变量就应该被分解为多个变量。

同一个变量承担两件不同的事情,会使代码阅读者迷惑。

做法

1.在待分解临时变量的声明及其第一次被赋值处,修改其名称;并将新变量声明为final

2.依次类推修改后续临时变量

七、Remove Assigments to Parameters(移除对参数的赋值)

代码对一个参数进行赋值,以一个临时变量取代该参数的位置

示例

重构前

int discount(int inputVal,int quanity,int yearToDate){

if(inputVal > 50) inputVal -=2; 

}

重构后

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

int result = inputVal;

if(inputVal > 50) result -= 2 ;

}

动机

对参数赋值意思是“参数被改变而指向另外一个对象”,之所以要对这种情况重构,因为它降低了代码的清晰度,而且混用了按值和按引用传参的方式,java的传参是按值传递;

做法

1.建立一个临时变量,把待处理的参数赋值给它

2.以“对参数的赋值”为界限,将其后所有对此参数的引用点,全部替换为“对此临时变量的引用”

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

一般我们不经常使用fianl来修饰参数,因为我发现,对于提高端函数的清晰度,这个办法并无太大的帮助。通常在较长的函数中使用final修饰,检查参数是否被修改

八、Replace Method with Method Object(以函数对象取代函数)

有一个大型函数,其中对局部变量的使用使你无法采用Etract Method

将这个函数放进一个单独的对象中,如此一来局部变量就成了对象内部的字段。然后你可以在对象内部对这个函数拆分提炼

示例

重构前

Class Account

int gamma(int inputVal,int quantity,int yearToDate){

int importantValue1 = (inputVal * quantity ) + delta();

int importantValue2 =  (inputVal * yearToDate ) + 100;

if( (yearToDate - importtantValue1) 》 100)

importantValue2 -= 20;

int importantValue3 = importantValue2 * 7;

// and so on

return importantValue3 - 2 * importantValue1

}

重偶后

class Gamma ...

private final Account _account;

private int inputVal;

private int quantity;

private int yearToDate;

private int importantValue1;

private int importantValue2;

private int importantValue3;

Gamma(Account source,int inputValue,int quantity,int yearToDate){

_account = source;

inputVal = inputValue;

quantity = quantity;

yearToDate = yearToDate;

}

现在可以把原本的函数搬到compute()了,函数中任何调用Account类的地方都改用_account字段

int compute(){

int importantValue1 = (inputVal * quantity ) +_account.delta();

int importantValue2 =  (inputVal * yearToDate ) + 100;

if( (yearToDate - importtantValue1) 》 100)

importantValue2 -= 20;

int importantValue3 = importantValue2 * 7;

// and so on

return importantValue3 - 2 * importantValue1

}

然后,修改就函数,让它委托给这个函数对象

int gamma(int inputVal,int quantity,int yearToDate){

return new Gamma(this,inputVal,quantity,yearToDate).compute());

}

这就是本项重构的基本原则,它带来的好处是:可以轻松的多compute() 函数重构,而不必要担心参数传递问题。

动机

重构的一个原则是代码简短明了,尽量使用小型函数;我们在面对大型函数时候,拆解会非常困难,这时考虑使用函数对象

做法

1.建立一个新类,根据待处理函数的用途命名这个函数。

2.在新类中新建一个final字段,用以保存原先大型函数所在的对象。我们将这个字段称为“源对象”。同时,针对原函数的每个临时变量和每个参数,在新对象中建立一个对应字段保存之。

3.在新类中建立一个构造函数,接受源对象及其所有参数作为参数

4.在新类中建立一个compute()函数

5.将原函数的代码复制到compute()函数中。如果需要调用元对象的的任何函数,请通过源对象调用

6。将旧函数的函数本体替换为这样一条语句:“创建上述新类对象,调用compute()函数”

九、Substitute Algorithm(替换算法)

想要把某个算法替换为另一个更清晰的算法,将函数本体替换为另一个算法。

示例

重构前

String foundPersion(String[] people){

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

if(people[i].equals("Dom")){

return "Dom";

}

if(people[i].equals("John")){

return "John";

}

if(people[i].equals("Rent")){

return "Rent";

}

}

return "";

}

重构后

String foundPersion(String[] people){

List candidates = Arrays.asList(nes String[]("Dom","John","Rent"));

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

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

return people[i];

}

}

return "";

}

动机

解决问题会有几种方案,总会方案最优,我们就需要不断更新需求

做法

1.准备好另一个算法,

2.正对现有测试,测试通过即可
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  重构 优化