重构-函数

本篇主要介绍9种方法对函数进行重构,包括以下几个方面:

函数:

  • 1 入参
  • 2 临时变量
  • 3 算法
  • 4 结构
  • 5 注释
  • 6 返回值

方法:

  • 1 提炼函数
  • 2 内联函数
  • 3 内联临时变量
  • 4 函数代替临时变量
  • 5 引入解释性变量
  • 6 重复使用临时变量
  • 7 禁止参数赋值
  • 8 对象代替变量
  • 9 替换算法

基础

基础方法强调将具有独立逻辑的代码封装到一个函数,并给函数起一个能表达其功能的名字。目的很简单,就是为了去掉“注释”。

1. 提炼函数

主要是针对那些函数体包含太多逻辑代码,可以考虑将其中具备一定独立逻辑的代码提炼成一个函数。这样做的好处是:(1)一个适当的函数名字可以省略一段注释,(2) 越小规格的分解,越容易将来被复用。

示例:

    public int increaseSalary(Person person){
        if( person.score > 90) {
            person.salary += 1000;
        } else {
            person.salary += 500;
        }

        /**
         * Print person information
         */
        System.out.println("Person name: " + person.name);
        System.out.println("Person age: " + person.age);
        System.out.println("Person position: " + person.position);
        System.out.println("Person salary: " + person.salary);
    }

好的代码应该一目了然,如果通过函数名就能知道它的作用,那为什么还要加注释呢?!

    public int increaseSalary(Person person){
        if( person.score > 90) {
            person.salary += 1000;
        } else {
            person.salary += 500;
        }

        printPersonInfo(person);
    }

    private void printPersonInfo(Person person){
        System.out.println("Person name: " + person.name);
        System.out.println("Person age: " + person.age);
        System.out.println("Person position: " + person.position);
        System.out.println("Person salary: " + person.salary);
    }

提炼函数适用度极高。在项目初期,在需求驱动下,很少有人能顾忌到代码结构,很容易将一大坨逻辑塞到一个函数下,使得函数变得很臃肿。其中逻辑很难一眼看清楚,难免存在很多bug。在代码review的时候,就可以考虑用该方法为函数瘦身。这里有一点要说的是,特别对于国内的开发来说,如果不能合适的命名一个提炼出来的函数,那就不暂时不要动了。一个函数应该涵盖一个完整的功能。

2. 内联函数

内联函数概念通俗来讲就是把上一个方法提炼的函数塞回去。第一种方法好处有百般,为什么还需要第二种方法。这里有两种情况,第一种是有人过于喜欢运用第一种方法,把项目代码细分的过于琐碎。也就是发力过度,这时候就需要用该方法还原。第二种是我们发现一个项目组织的乱七八糟,我们想对它进行重构,但是它已经乱到无从下手了,这时候就可以先把零碎的函数全部揉成一个,理清逻辑,再做合适的拆分。


进阶

进阶方法是针对变量进行优化,进一步精简函数。

3. 内联临时变量

函数体中经常会出现大量的临时变量,其中有一些仅仅是为了记录某一计算结果。这种变量通常被赋值了一次。

    public int increaseSalary(Person person){
        double score = person.getScore();
        if( score > 90) {
            person.salary += 1000;
        } else {
            person.salary += 500;
        }
    }

这种情况,在项目中随处可见。个人理解什么时候我们该用这种方法:(1)这个变量赋值一次之后,没有再次赋值,(2)getScore方法代价极小,并且出现的次数并不多,至于多少算不多,需要自己把控。

    public int increaseSalary(Person person){
        if( person.getScore() > 90) {
            person.salary += 1000;
        } else {
            person.salary += 500;
        }
    }
4. 函数代替临时变量

方法 3 中我们是直接用函数代替了一个临时变量,但有时候一个临时变量是通过另外两个变量计算得来的,有可能是两个成员变量。这个时候可以对它抽象出一个函数来在运用方法3。使用场景参考方法3.

    public int increaseSalary(Person person){
        double score = baseScoreRate * person.score;
        if( score > 90) {
            person.salary += 1000;
        } else {
            person.salary += 500;
        }
    }

临时变量的滥用,显然会驱使我们写出很长的函数。将临时变量替换为一个函数,一方面也是为了其他成员函数的使用。

    public int increaseSalary(Person person){
        if( getScore(person) > 90) {
            person.salary += 1000;
        } else {
            person.salary += 500;
        }
    }

    private double getScore(Person person){
        return  _baseScoreRate * person.score;
    }
5. 引入解释性变量

该方法可以说是对方法4的逆操作,从例子中就能感受出区别。

    public int increaseSalary(Person person){
        if((person.getCert().indexof("master") > 1) &&
                (person.getAge().toInt() > 35) &&
                (person.getScore().compare() < 10)) {
            person.salary += 1000;
        } else {
            person.salary += 500;
        }
    }

添加三个临时变量,使得后续逻辑更加清晰。代码怎么写,计算机都能读懂,重要的是,要让别人读懂。final变量可以限制该值只能赋值一次,合适的运用可以让编译器帮我们预防许多未知错误。

    public int increaseSalary(Person person){
        final boolean isCert = person.getCert().index("master") > 1;
        final boolean isOlder = person.getAge().toInt() > 35;
        final boolean isScore = person.getScore().compare() < 10;

        if (isCertificate && isOlder && isScore) {
            person.salary += 1000;
        } else {
            person.salary += 500;
        }
    }
6. 重复使用临时变量

这个意思指的是有一个变量被反复赋值,而它并不是循环变量也不是用于收集计算结果。看例子:

int tmp = 2 * ( height + width);
System.out.println( tmp );
tmp = heithg * width;
System.out.println( tmp );

这个现象在国内代码中出现比较多,看下开源项目,基本不存在这个问题。主要就是语言问题,找不到一个合适的词对变量命名,想到的第一个一般都是temp。

final double perimeter = 2 * ( height + width); 
System.out.println( tmp );
final double ara = height * width;
System.out.println( are );
7. 禁止参数赋值

对参数赋值,主要是针对像JAVA这样按值传递的语言,在代码中对传进来值修改,可能会造成误解,因为实际上在函数内怎么修改都不会影响原先参数。为了避免不必要的麻烦,最好不要对形参直接修改。

public int getResult( int input) {
    if( input > 2) {
        input -= 1;
    }
    return input;
}

示例代码并没有什么意义,就是为说明之前描述的情景。这种方式最大的问题是混淆了按值传递和按引用传递两种方式。

public int getResult( int input) {
    int result = input;
    if( input > 2) {
        result -= 1;
    }
    return result;
}
8. 对象代替变量

假如我们看到一个函数,里面糅杂着大量的变量和复杂的计算过程,我们可以通过创建一个新类,将其中所有变量作为新类的成员变量。

    public class Calc {

        public int calcSome(int one, int two, int three) {

            int importantValue1 = (one * two) + getScore();
            int importantValue2 = (two * three) + 1046;

            if( importantValue1 > 100){
                 importantValue2 -= 1000;
            }
            /**
             * and so on.
             */ 
            return importantValue1 + importantValue2;
        }
    }

减少大量变量定义,可以提高代码可读性,将计算过程抽象出来,将来也可以方便的替换。

    public class Calc{
        public int calcSome(int one, int two, int three) {
            Num num = new Num(this, one, two, three );
            return new Num(this, one, two, three ).compute();
        }
    }

    class Num {
        private final Calc calc;
        private double primaryNum;;
        private double secondaryNum;
        private double tertiaryNum;

        public Num(Calc calc, double primaryNum, double secondaryNum, double tertiaryNum) {
            this.calc = calc;
            this.primaryNum = primaryNum;
            this.secondaryNum = secondaryNum;
            this.tertiaryNum = tertiaryNum;
        }

        int compute(){
            int importantValue1 = (one * two) + calc.getScore();
            int importantValue2 = (two * three) + 1046;

            if( importantValue1 > 100){
                 importantValue2 -= 1000;
            }
            /**
             * and so on.
             */ 
            return importantValue1 + importantValue2;
        }
    }
9. 替换算法

以下代码还是经常能看到的,一个简单的多路选择过程,用下述方式和swith都可以实现,但是无论哪一种,都应该被避免。switch最大的问题就是重复,在面向对象语言有多态的概念之后,就应该尽量避免使用switch。

    String foundSomeone( String[] things ){
        for( int i = 0; i < things.length; i++){
            if( things[i].equals("apple")){
                return "apple";
            }
            if( things[i].equals("balana")){
                return "balana";
            }
            if( things[i].equals("pineapple")){
                return "pineapple";
            }
        }
        return "";
    }

重构后:

    String foundSomeone( String[] things ){
        List candidates = Arrays.asList(new String[] {"apple", "balana", "pineapple"});

        for( int i = 0; i < things.length; i++){
            if (candidates.contains(things[i])) {
                return things[i];
            }
        }
        return "";
    }

上述9个方法针对函数全方位优化。方法终归是方法,如何使用得当才是考验。很多方法都是成对出现,显然存在矫枉过正的情况。那么怎么才能融会贯通呢?首先,熟记于心,其次带着9个方法去读开源代码,去修改自己代码。假以时日,这招亢龙有悔的悔自决就练到家了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值