重构-改善既有代码的设计(六):重新组织函数

1、提炼函数(Extract Method)

(1)若有一段代码可以被组织在一起并独立出来,就把这段代码放进一个独立的函数,并让函数名称解释该函数的用途

(2)提炼函数的契机:过长函数、需要注释才能让人理解

(3)注意函数名称和函数本体之间的语义距离

(4)创造一个新函数,要根据它“做什么”来命名,而不是以它“怎么做”命名

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

(6)检查被提炼的代码块,是否有任何局部变量的值被它改变

(7)如果被提炼代码块只是读取局部变量,并不修改它们,这种情况就可以简单的将它们当作参数传给目标函数

(8)被提炼出来的代码块组成新的目标函数,如果被提炼代码块对局部变量赋值,这种情况相对复杂,可以分为两种情况:

  1. 如果变量仅仅在被提炼代码块中使用,可以将这个临时变量的声明移到被提炼代码块中
  2. 如果被提炼代码块之外也使用了这个变量,这又分为两种情况
  • 如果这个变量在被提炼代码块之后未再被使用,直接在提炼出来的代码块中修改就可以了
  • 如果被提炼代码块之后的代码还使用了这个变量,就需要让目标函数返回该变量改变后的值

(9)如果需要返回的变量不止一个怎么办?

答:挑选另一块代码来提炼,安排多个函数,用以返回多个值

2、内联函数(Inline Method)

(1)如果提炼的函数内部代码和函数名称同样清晰易读,就应该去掉这个函数,减少非必要的间接性

(2)使用内联手法,找出有用的间接层,同时消除那些无用的间接层

  • 检查函数,确定它不具有多态性,如果子类继承了这个函数,就不要将此函数内联
  • 找出这个函数的所有被调用点
  • 将这个函数的所有被调用点都替换成函数本体
  • 编译测试
  • 删除该函数的定义

3、内联临时变量(Inline Temp)

(1)如果一个临时变量只被简单表达式赋值一次,就将该变量替换成表达式本身

(2)如果某个临时变量妨碍了其他重构手法,例如Extract Method ,就应该将这个变量内联化

  • 检查给临时变量赋值的语句,确保等号右边的表达式没有副作用
  • 如果这个变量没有被声明为final,就将它声明为final,然后编译,检查该临时变量是否只被赋值一次
  • 找到该临时变量的所有引用点,将它们替换成相应的赋值表达式
  • 每次修改后,编译并测试
  • 修改完所有引用点之后,删除该临时变量的声明和赋值语句
  • 编译,测试

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

(1)由于临时变量是暂时的,只能在所属函数内使用,可以将临时变量对应的表达式提炼到独立函数,这样就可以被其他函数使用

(2)局部变量会使得代码难以提炼,应该尽可能把它们替换成查询

double getPrice(){
    int basePrice = quantity * itemPrice;
    double discountFactor;
    if(basePrice > 1000){
        discountFactor = 0.95;
    }else{
        discountFactor = 0.98;
    }
    return basePrice * discountFactor;
}

// 修改后
double getPrice(){
    return basePrice() * discountFactor();
}
private int basePrice(){
    return quantity * itemPrice;
}
private double discountFactor(){
    if(basePrice() > 1000){
        return 0.95;
    }else{
        return 0.98;
    }
}

5、引入解释性变量(Introduce Explaining Variable)

(1)如果表达式过于复杂,应该将该复杂表达式或者一部分放进一个临时变量,以此变量的名称来解释表达式的用途

(2)常见应用场景:

  • 在条件逻辑中,重构将每个条件子句提炼出来,以一个良好命名的临时变量解释对应的条件子句
  • 在较长算法中,可以运用临时变量来解释每一步运算的意义

(3)注意临时变量有一定局限性,只在它所处的那个函数中才有意义

(4)有时候当局部变量使得提取函数难以进行时,就会引入解释性变量的方式

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

(1)对于某些被赋值超过一次的临时变量,应当替换(分解)为多个临时变量,每个变量只承担一个责任

(2)特殊情况:循环变量、结果收集变量

范例:计算苏格兰布丁运动的距离
double getDistanceTravelled(int time){
    double result;
    double acc = primaryForce / mass;
    int primaryTime = Math.min(time, delay);
    result = 0.5 * acc * primaryTime * primaryTime;
    int secondaryTime = time - delay;
    if(secondaryTime > 0){
        double primaryVel = acc * delay;
        acc = (primaryForce + secondaryForce) / mass;
        result += primaryVel * secondaryTime + 0.5 * acc * secondaryTime * secondaryTime;
    }
    return result;
}
说明:上述代码中acc变量被赋值两次,acc变量有两个责任:第一是保存第一个力造成的初始加速度;
第二是保存两个力共同造成的加速度。
----------------------------------------------------------------------------------
修改后的代码如下:
double getDistanceTravelled(int time){
    double result;
    final double primaryAcc = primaryForce / mass;
    int primaryTime = Math.min(time, delay);
    result = 0.5 * primaryAcc * primaryTime * primaryTime;
    int secondaryTime = time - delay;
    if(secondaryTime > 0){
        double primaryVel = primaryAcc * delay;
        final double secondaryAcc = (primaryForce + secondaryForce) / mass;
        result += primaryVel * secondaryTime + 0.5 * secondaryAcc * secondaryTime * secondaryTime;
    }
    return result;
}

7、移除对参数的赋值(Remove Assignments to Parameters)

(1)不要对函数的入参进行赋值,降低了代码的清晰度,而且混用了按值传递和按引用传递这两种参数传递方式(Java只采用按值传递)

int discount(int inputVal, int quantity, int yearToDate){
    if(inputVal > 50){
        inputVal -= 2;
    }
    if(quantity > 100){
        inputVal -= 1;
    }
    if(yearToDate > 10000){
        inputVal -= 4;
    }
    return inputVal;
}
---------------------------------------------------------------------------
修改后的代码
int discount(int inputVal, int quantity, int yearToDate){
    int result = inputVal;
    if(inputVal > 50){
        result -= 2;
    }
    if(quantity > 100){
        result -= 1;
    }
    if(yearToDate > 10000){
        result -= 4;
    }
    return result;
}

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

(1)如果因为局部变量的缘故,对一个大型函数无法采用提炼函数(Extract Method),可以将这个函数放进一个单独对象,这样的话,局部变量就成了对象内的字段

(2)由于局部变量变成了字段,所以可以分解这个大型函数,不必传递参数

public int gamma(int inputValue, int quantity, int yearToDate) {
    int importantValue1 = inputValue * quantity + delta();
    int importantValue2 = inputValue * yearToDate + 100;
    if ((yearToDate - importantValue1) > 100) {
        importantValue2 -= 20;
    }
    int importantValue3 = importantValue2 * 7;
    return importantValue3 - 2 * importantValue1;
}

-----------------------------------------------------------------------
修改之后,如下
public class Gamma {
 
	private final Test test;
	private int inputValue;
	private int quantity;
	private int yearToDate;
	private int importantValue1;
	private int importantValue2;
	private int importantValue3;
 
	public Gamma(Test test, int inputValue, int quantity, int yearToDate) {
		super();
		this.test = test;
		this.inputValue = inputValue;
		this.quantity = quantity;
		this.yearToDate = yearToDate;
	}


	public int compute() {
		importantValue1 = inputValue * quantity + test.delta();
		importantValue2 = inputValue * yearToDate + 100;
           importantThing();
		importantValue3 = importantValue2 * 7;
		return importantValue3 - 2 * importantValue1;
	}
    private void importantThing(){
        if ((yearToDate - importantValue1) > 100) {
			importantValue2 -= 20;
		}
    }
}

public int gamma(int inputValue, int quantity, int yearToDate) {
	return new Gamma(this,inputValue,quantity,yearToDate).compute();
}

9、替换算法(Substitute Algorithm)

(1)把某个算法替换成另一个更清晰的算法

  • 准备好另一个(替换用)算法,让它编译通过
  • 针对现有测试,执行上述的新算法,如果结果与原本结果相同,重构结束

 

尾注

  • 上述的总结与思考是基于对《重构—改善既有代码的设计》这本书的精读与演绎
  • 更多及时干货,请关注微信公众号:JAVA万维猿圈

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值