天涯旅店

使用 if-else 条件判断的常见问题和优化方案

最近阅读项目的历史代码,发现一个简单但是非常普遍的问题,过多的判断逻辑,导致代码非常臃肿,后面维护的人员读起来很吃力。

常见语法


过多嵌套

项目前期开发任务紧,版本迭代快,也不怎么考虑逻辑优化,来一个需求,就加一个 if,久而久之,就串成了一座金字塔。

if (true) {
    if (true) {
        if (true) {
            if (true) {
                if (true) {
                    if (true) {
                        if (true) {
                            if (true) {
                            }
                        }
                    }
                }
            }
        }
    }
}

上面的写法应该还挺常见的,我就是在项目中看到下面这个情况,才想写到这篇文章的。

if-else-1

死守一个出口

有时候过于遵循方法的“单个方法出口”,即仅从单个位置返回,反而使代码可读性变差。

if (ture) {
    result = "x";
} else {
    if (ture) {
        result = "y";
    } else {
        result = "z";
    }
}
// this return is single and lonely
return result ;

完全按逻辑顺序编码

顺着逻辑顺序来,第一个条件附带一大串操作,第二个条件只有较少操作。

if (true) {
    doSomethingA();
    doSomethingB();
    doSomethingC();
    doSomethingD();
    doSomethingE();
    doSomethingF();
    doSomethingG();
    doSomethingH();
    doSomethingI();
    doSomethingJ();
    doSomethingK();
    doSomethingL();
    doSomethingM();
    doSomethingN();
    return true;
}
doSomethingO();
return false;

优化方法


当出现这些问题,代码已经复杂到接手的人无法理解、难以维护的程度后,只能狠下心重构优化。
那有什么方案可以优雅地优化掉这些多余的 if-else 呢?

尽早 return

在发生多层嵌套导致多次缩进的情况下,很基本的想法就是将条件判断取反,得到一个更清晰的逻辑,特别是在代码逻辑里需要处理多种特殊情况的时候。

极客时间的专栏课程里学到了这个方法的概念描述:以卫语句取代嵌套的条件表达式 (Replace Nested Conditional with Guard Clauses)

if (condition) {
    if (condition1) {
        /* do somethingA */
    } else {
        return(abc);
    }
    /* do somethingB */
} else {
    return(xyz);
}

替换成下面这种,代码逻辑清晰多了。

if (!condition) {
    return(xyz);
}
if (!condition1) {
    return(abc);
}
/* do somethingA */
/* do somethingB */

这是在处理 if-else 语句时最常用也是最有效的一种方法。

与任何编程实践一样,不应将某个规则视为必须始终遵守的硬性规则。尽早 return 对小功能影响不大,甚至可能增加认知负担,但是,随着函数的大小和复杂性的增加,尽早 return 的逻辑扁平化优势变得越来越重要。

策略模式

有这么一种场景,根据不同参数走不同的逻辑,其实这种场景很常见。最一般的实现:

if (strategy.equals("fast")) {
    /* 快速执行 */
} else if (strategy.equals("normal")) {
    /* 正常执行 */
} else if (strategy.equals("smooth")){
    /* 平滑执行 */
} else if (strategy.equals("slow")){
    /* 慢慢执行 */
}

一般的处理有以下两种:

多态

interface Strategy {
    void run() throws Exception;
}

class FastStrategy implements Strategy {
    @Override
    void run() throws Exception {
        /* 快速执行逻辑 */
    }
}

class NormalStrategy implements Strategy {
    @Override
    void run() throws Exception {
        /* 正常执行逻辑 */
    }
}

class SmoothStrategy implements Strategy {
    @Override
    void run() throws Exception {
        /* 平滑执行逻辑 */
    }
}

class SlowStrategy implements Strategy {
    @Override
    void run() throws Exception {
        /* 慢速执行逻辑 */
    }
}

具体策略对象存放在一个 Map 中,优化后的实现:

Strategy strategy = map.get(param);
strategy.run();

上面这种优化方案有一个弊端,为了能够快速拿到对应的策略实现,需要 Map 对象来保存策略;当添加一个新策略的时候,需要重新加载 Map,否则新策略容易被忽略。

枚举

发现很多同学不知道在枚举中可以定义方法,这里定义一个表示状态的枚举,另外可以实现一个 run 方法。

public enum Status {
    NEW(0) {
        @Override
        void run() {
            /* do something */
        }
    },
    RUNNABLE(1) {
        @Override
        void run() {
            /* do something */
        }
    };
    public int statusCode;
    abstract void run();
    Status( int statusCode ) {
        this.statusCode = statusCode;
    }
}

重新定义策略:

public enum Strategy {
    FAST {
        @Override
        void run() {
            /* do something */
        }
    },
    NORMAL {
        @Override
        void run() {
            /* do something */
        }
    },
    SMOOTH {
        @Override
        void run() {
            /* do something */
        }
    },
    SLOW {
        @Override
        void run() {
            /* do something */
        }
    };
    abstract void run();
}

通过枚举优化之后的代码如下:

Strategy strategy = Strategy.valueOf(param);
strategy.run();

JDK 8 学会使用 Optional

如果登录用户为空,执行 action1,否则执行 action2,最常见的实现逻辑:

if (user == null) {    
    //do action 1} 
else {    
    //do action2
}

使用 Optional 优化之后,让非空校验更加优雅,间接的减少 if 操作。

Optional<User> userOptional = Optional.ofNullable(user);
userOptional.map(action1).orElse(action2);

数组小技巧

来自 Google 解释,这是一种编程模式,叫做表驱动法。
本质是从表里查询信息来代替逻辑语句,比如有这么一个场景,通过月份来获取当月的天数。

int getDays(int month){
    if (month == 1)
        return(31);
    if (month == 2)
        return(29);
    if  month == 3)
        return(31);
    if (month == 4)
        return(30);
    if (month == 5)
        return(31);
    if (month == 6)
        return(30);
    if (month == 7)
        return(31);
    if (month == 8)
        return(31);
    if (month == 9)
        return(30);
    if (month == 10)
        return(31);
    if (month == 11)
        return(30);
    if (month == 12)
        return(31);
}

优化后代码:

int monthDays[12] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
int getDays(int month) {
    return(monthDays[--month]);
}

总结


if-else 作为每种编程语言都不可或缺的条件语句,在编程时会大量用到。如果一段代码存在过多的 if-else 嵌套,可读性就会急剧下降,后期维护难度也大大提高。

所以如果写代码时需要使用到 if,我们可以尝试避免使用 else。在处理多层嵌套情况下,用其他技术替代 else 关键字,这样代码的可读性能够大大提高。

参考文章 avoid-else-return-early[1].

没有标签
首页      未分类      使用 if-else 条件判断的常见问题和优化方案

发表回复

textsms
account_circle
email

天涯旅店

使用 if-else 条件判断的常见问题和优化方案
最近阅读项目的历史代码,发现一个简单但是非常普遍的问题,过多的判断逻辑,导致代码非常臃肿,后面维护的人员读起来很吃力。 常见语法 过多嵌套 项目前期开发任务紧,版…
扫描二维码继续阅读
2020-11-21