重构改善既有代码的设计《六》重新组织函数
6.1Extract Method(提炼函数)
1,你有一段代码可以被组织在一起并独立出来
将则这段代码房间一个独立函数中,并让函数名称解释该函数的用途
void printData(double amount){
printBanner();
//print detail
System.out.println("name:"+_name);
System.out.println("amount:"+amount);
}
修改成:
void printData(double amount) {
printBanner();
printDetail(amount);
}
void printDetail(double amount) {
System.out.println("name:" + _name);
System.out.println("amount:" + amount);
}
2,动机
- 如果每个函数的粒度都很小,那么函数复用的机会就很大
- 这会使高层函数读起来象一系列注释
- 如果函数都是细颗粒度,那么覆写也会更加容易
3,做法 - 创建一个新函数,根据这个函数的意图命名(做什么)
- 将提炼的代码从原函数复制到新建目标函数中
- 检查是否有“仅用于被提炼代码段”的临时变量,如果有,在目标函数将它们声明成临时变量
- 检查被提炼代码段,看看是否有任何局部变量的值被他改变
- 将被提炼代码段中需要的读取的局部变量,当作参数传给目标函数
- 处理完所有的局部变量之后,进行编译
- 在原函数中,将被提炼代码段替换为对目标函数的调用
- 编译,测试
4,疑问 - 如果需要返回的变量不止一个?
挑选另外一个代码来提炼,尽量每一个函数都只返回一个值
6.2Inline Method (内联函数)
1,一个函数的本体与名称同样清楚易懂
在函数调用点插入函数本体,然后移除该函数
int getRating() {
return (moreThanFiveLateDeliveries())?2:1;
}
boolean moreThanFiveLateDeliveries(){
return _numberOfLateDeliveries>5;
}
修改为:
int getRating() {
return (_numberOfLateDeliveries>5)?2:1;
}
2,动机
- 有时候某些函数,其内部代码和函数名称同样清晰易读
- 组织不甚合理的函数,可以将他们内联到一个大型函数,再提炼出组织合理的小型函数
3,做法 - 检查函数,确定不具备多态性
如果子类继承了这个函数,就不要将此函数内联,因为子类无法覆写一个根本不存在的函数, - 找出这个函数的调用点,
- 将这个函数的所有的调用点换成函数主体
- 编译,测试
- 删除之前的函数
6.3Inline Temp(内联临时变量)
1,你有一个临时变量,只被一个简单的表达式赋值一次,而它妨碍了其他重构手法
将所有对该变量的引用动作,替换为对它赋值的那个表达式自身
double basePrice=anOrder.basePrice();
return basePrice>1000;
修改为
return anOrder.basePrice()>1000;
2,动机/做法 (略)
6.4Replace Temp With Query(以查询取代临时变量)
1,你的程序以一个临时变量保存某一表达式的运算结果
将这个表达式提炼到一个独立函数中,将这个临时变量的所有的引用点替换为对新函数的调用,此后,这个新函数就可能被其他的函数引用
double basePrice = _quanlity * _itemPrice;
if (base > 1000)
return basePrice * 0.95;
else
return basePrice * 0.7;
修改为:
double basePrice = _quanlity * _itemPrice;
if (base > 1000)
return basePrice() * 0.95;
else
return basePrice() * 0.7;
}
double basePrice () {
return _quanlity * _itemPrice;
}
2,动机
临时变量的问题:它们是暂时的,而且只能所属函数使用
技巧:如果临时变量替换为一个查询,那么同一个类的所有的函数都可以获取这份信息
3,做法
- 找出只被赋值一次的临时变量
- 将该临时变量声明为final
可确保临时变量的确只是被赋值一次 - 编译
- 将“对该临时变量赋值”之语句的等号右侧部分提炼到一个独立函数
1)首先将函数声明为private
2)确保提炼出来的函数无任何副作用 - 编译测试
- 在该变量实施Inline Temp
6.5Introduce Explaining Variable(引入解释性变量)
1,你有一个复杂的表达式
将该复杂的表达式(或其中一部分)的i俄国放进一个临时变量,以此变量名称来解释表达式用途
if (platform.toUppercase().indexOf("MAC")>-1)&&platform.toUppercase().indexOf("IE")>-1&&wasInitialized()&&resize>0{
//doSomething
}
修改为
final boolean isMacOs=platform.toUppercase().indexOf("MAC")>-1;
final boolean isIEBrowser=platform.toUppercase().indexOf("IE")>-1;
final boolean wasResized=resize>0;
if (isMacOs&&isIEBrowser&&wasInitialized()&&wasResized){
//doSomething
}
2,动机
在条件逻辑中,引入解释性变量特别有价值,你可以用这项重构将每个条件子句提炼出来,以一个良好命名的临时变量来解释对应条件子句的意义,使用这项重构的另一种情况,在较长算法中,可用临时白能量来解释每一步运算的意义
3,做法
- 声明一个final临时变量,将待分解之复杂表达式中的一部分动作的运算结果赋值给他
- 将表达式中的运算结果这一部分,替换为上述临时变量
如果被替换的这一部分在代码中重复出现,你可以每次一个,逐一替换 - 编译测试
- 重复上述过程,处理表达式的其他部分
4,什么时候使用
在Extract Method 需要胡斐更大工作量的时候,处理一个拥有大量局部变量的算法
6.6Split Temporary Variable(分解临时变量)
1,你的程序某个临时变量赋值超过一次,它既不是循环变量,也不被用于收集计算结果
针对每次赋值,创造一个独立,对应的临时变量
double temp=2*(_height+_wideth);
System.out.println(temp);
temp=_height*_wideth;
System.out.println(temp);
修改为:
final double perimeter=2*(_height+_wideth);
System.out.println(perimeter);
final double area=_height*_wideth;
System.out.println(area);
2,动机
同一个变量承担不同的事情,会令代码阅读者迷惑
循环变量:会随循环次数变化而变的变量
结果收集变量:“通过整个函数的运算"而构成某一个值收集起来
3,做法
- 在待分解的来闹事变量声明及其第一次被赋值处,修改其名称
- 将新的临时变量声明为final
- 以该临时变量的第二次赋值动作为界,修改此前对该临时变量的所有的引用点,让他们引用新的临时变量
- 在第二次赋值出处,重新声明原先那个临时变量
- 编译测试
- 重复
6.7Remove Assignments Parameter(移除对参数的赋值)
1,代码对一个参数进行赋值
以一个临时变量取代该参数的位置
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;
}
还可以为
int discount(final int inputVal,final int quantity,final int yearToDate){
int result=inputVal;
if(inputVal>50) result-=2;
}
2,动机
降低代码的清晰度,而且混肴按值传递和按引用传递这两种参数传递方式
在java中,不要对参数赋值
3,做法(略)
6.8Replace Method with Method Object(以函数对象取代函数)
你有一个大型函数,其中对局部变量的使用无法采用Extract Method
将则这个函数放进一个单独的对象中,如此一来局部变量就成了对象内的字段,然后你可以在同一对象中将这个大型函数分解为多个小型函数
class Order...
double price() {
double primaryBasePrice;
double secndaryBasePrice;
double tertiaryBasePrice;
//long computation
....
}
2,动机
当一个变量中局部变量泛滥的时候,函数对象
3,做法
- 建立一个新类,根据待处理函数的用途,为此函数命名
- 在新类中建立一个final字段,用以保存原先大型函数所在的对象,我们将这个对象称为源对象,同时,针对原函数的每一个临时变量和每一个参数,在新类中建立一个对应得字段保存之
- 在新类中建立一个构造函数,接受源对象及原函数的所有参数为参数
- 在新类建立一个compute()函数
- 将原函数的代码复制到compute()函数中,如果需要调用源对象的任何函数,请通过源对象字段diaoyong
- 编译
- 将旧函数的函数体替换为这样一条语句“创建上述新类的一个新对象,而后调用其中compute()函数”
4,补充
修改旧函数,让它将他的工作委托刚刚完成这个函数对象
好处:可以轻松对compute函数采取Extract Method,不必担心参数传递问题
6.9Substitute Algorithm(替换算法)
如果你想把某个算法替换为另一个更清晰的算法
将函数本体替换为另一个算法
注意:结果一定要保持相同