重构方法之重新组织函数
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);
}
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.正对现有测试,测试通过即可
示例
有一段代码可以被组织在一起并独立出来重构前:
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.正对现有测试,测试通过即可
相关文章推荐
- 重构-改善既有代码的设计:重新组织函数的九种方法(四)
- 重构-改善既有代码的设计:重新组织函数的九种方法(四)
- 重构-改善既有代码的设计:重新组织函数的九种方法(四)
- 《重构改善既有代码的设计》之重构列表--重新组织函数(三)
- 重构-改善既有代码的设计:重新组织数据的16种方法(六)
- 《重构改善既有代码的设计》之重构列表--重新组织函数(二)
- 重构系列3.重新组织函数
- 【重构笔记02】重新组织函数
- 重构手法一重新组织函数
- 《重构改善既有代码的设计》之重构列表--重新组织函数(一)
- 2016书单总结--重构改善既有代码的设计--重新组织函数
- PHP 杂谈《重构-改善既有代码的设计》之一 重新组织你的函数
- 重构之重新组织函数
- 代码重构-重新组织函数
- 重构手法之重新组织函数【3】
- 重构改善既有代码设计--重构手法 之重新组织你的函数总结
- PHP 杂谈《重构-改善既有代码的设计》之一 重新组织你的函数
- PHP 杂谈《重构-改善既有代码的设计》之一 重新组织你的函数
- PHP 杂谈《重构-改善既有代码的设计》之一 重新组织你的函数【链接:http://www.cnblogs.com/baochuan/archive/2012/03/31/2425441.html】
- 代码重构之重新组织函数