重构心法修炼第一层:重新组织函数
6.1 提炼函数
将小的功能抽取为函数,并将其方法名作为注释,所以,需要在方法名上下一点功夫。
抽取小的功能的优势在于两点:(1)可复用这个小功能(2)便于重写
函数名应该以其意图而命名,而不是以如何去做命名。
函数名如此重要,若想不出一个可以表现其意图的名字,就先别动。
提炼函数遇到的最大的问题是:处理临时变量
(1)如果没有临时变量,那么可以直接抽取方法。比如 多个println语句,可以抽取为一个printBanner()“打印横幅”函数。
(2)如果仅仅是读取一个临时变量,对其不处理。也就是说,这个临时变量在提炼的函数中将不会被改变,那么可以直接将这个临时变量作为函数的参数进行传递。
(3)如果有个别地方用到了这个临时变量,那么可以将这个临时变量作为新提炼的方法的返回值。也就是最常见的用方法替换临时变量音引用点。对于重新提炼的方法而言,一般作为返回值的临时变量,习惯将其命名为result。
(4)如果这个变量作为返回值后,又被其它多个方法处理,那么也可保留这个变量。例如下面的实例代码
public void test(String URL) {
//方法内的临时变量
String path = "";
//解析URL
path = analysis(URL);
//多处地方用到这个临时变量path
File file = getFile(path);
Root root = getRoot(path);
}
public String analysis(String URL) {
String result = "";
//处理URL并赋值给result
return result;
}
6.2 内联函数 与 内联变量
当函数体较为简单的时候,可以就不需要单独抽取出来一个方法,直接使用表达式即可。
可以考虑将旧的临时变量,用赋值它的表达式替换。
return _number > 5 ? 1 : 0 ; 没必要把_number > 5 也抽取为方法
double basePrice = basePrice(); return basePrice > 1000; 内联变量后
return basePrice() > 1000;
6.4 以查询取代临时变量
你的程序以一个临时变量保存某一表达式的运算结果。将这个表达式提炼到一个独立函数中。将这个临时变量的所有引用点替换为对新函数的调用。此后,新函数就可以被其它函数调用。
解释:将某一表达式抽取为一个方法,这个方法的返回值参与了其它代码的处理。在重构其它代码的时候,若不以此处理,以前需要此临时变量的地方,都需要通过方法的参数接收。而现在,直接可以调用那个表达式的方法函数以替换 此前那个临时变量所处的地方。
这个"查询"的意思就是,在函数中调用函数,而不是将函数赋值到临时变量中,再通过临时变量传递给其它重构方法的参数,再调用这个临时变量。
重构手法:(1)将临时变量设置为final ,确保这个临时变量只能被赋值一次。(2)将临时变量右边的表达式抽取为一个独立的函数中。(3)内联临时变量,即将所有临时变量,用方法替换。(4)删除临时变量(5)测试
6.5 引入解释性变量
如果有一个很复杂的表达式。例如 if ( 复杂表达式A && 复杂表达式B && 复杂表达式C)。可以将复杂表达式的结果放到一个临时变量中。比如
final Boolean isA = 复杂表达式A;
final Boolean isB = 复杂表达式B;
final Boolean isC = 复杂表达式C;
那么这个语句就替换为了 if (isA && isB && isC)。
临时变量的优势可能就能体现在这里。但是《重构》这本书里面对临时变量深恶痛绝。最终,还是用方法来替换复杂表达式ABC,还是变成了 if(isA() && isB() && isC())。所以,在方法名上需要下点功夫。
最终的重构思路就是,如果某个方法内部含有大量的临时变量与复杂表达式,可先将复杂表达式用解释性变量替换。再将解释性变量用方法替换。最后再采取其它重构手段处理剩余的临时变量,如用查询取代临时变量。
6.6 分解临时变量
既不是循环变量,也不用于收集计算结果的临时变量,不应该被赋值超过一次。
针对每次赋值,创造一个独立的临时变量。所以,一般在初始化临时变量的时候,加上final语句还是比较好的。
接下来,尽情的重构吧!
6.7 移除对参数的赋值
尽量不要对传递进来的参数做修改,用一个 新的临时变量接收这个参数,并用一个result返回。例如
int discount(int inputVal, int quality, int yearToDate) {
int result = inputVal;
if (inputVal > 50) {
result -= 2;
}
return result;
}
6.8 以函数对象取代函数
你有一个大型函数,其中对局部变量的使用使你无法采用 Extract Method ,即抽取方法。
将这个函数放进一个单独的对象中,如此一来局部变量就成了对象内的字段。然后你可以在同一个对象中将这个大新函数分解为多个小型函数。
也就是说,可创建一个内部类对象。大型函数中的局部变量将作为内部类对象的成员变量。在内部类构造器中接收原来的外部类对象,以及方法的参数。以前方法内产生的临时变量,将作为内部类的成员变量。然后对内部类进行重构就简单的多,因为不需要考虑参数传递的问题,在内部类中重构代码,直接获取的就是内部类自己的成员,所以不需要从方法的参数上传递。
class Account
public int gamma(Long a , Long b , Long c){
//总之,这个方法内有大量的临时变量产生
Long aaa = a * 3;
Long bbb = b * 3;
Long ccc = c * 3;
return handler(aaa) * handler(bbb) * handler(ccc);
}
//-------------------重构后的代码-----------------------------
class Account
int gamma(Long a , Long b , Long c){
return new gamma(this,a,b,c).compute();
}
class gamma{
private final Account account ;
private Long a;
private Long b;
private Long c;
private Long aaa;
private Long bbb;
private Long ccc;
class gamma(Account account , Long a , Long b , Long c){
this.account = account;
this.a = a;
this.b = b;
this.c = c;
}
int compute(){
aaa = a * 3;
bbb = b * 3;
ccc = c * 3;
//这个时候,抽取上面的三行代码作为一个方法居然变成了无局部变量的处理手段,开始重构吧!
return account.handler(aaa) * account.handler(bbb) * account.handler(ccc);
}
}
6.9 替换算法
如果有更容易的方法,那么就用那个容易的方法。例如StringUtils工具类。