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

    xiaoxiao2023-10-18  182

    1、提炼函数(Extract Method)

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

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

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

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

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

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

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

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

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

    (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)