如何解决if else多层嵌套

前言

在进入正题前请允许我啰嗦几句,其实这篇文章我在上一年就想写了,但是拖延症发作,一直拖到今天。

if...else我们都很熟悉,并且应该也是写逻辑代码用得最多的,我的大学java老师在讲if else语句的时候和我们说过if else嵌套应尽量保持不超过3层,这句话,我一直记住了,但是在实际写代码中,我是尽量保持在最多两层嵌套,大多数情况下,我是保持在一层判断。

有人也许会觉得多层嵌套也对代码没多大的影响,但其实开发更多是一个团队的工作,如果你只是一味考虑自己实现的简便性,而忽略程序的结构清晰、可读性、可拓展性和可维护性,那将会降低代码的可阅读性。

为什么要避免if else多层嵌套

想想当你看到如下代码时,你有怎么样的感觉。

if(type == MetaDataConstant.TYPE_01){

    if(number == 1){
        if(isShow){

        }else{

        }
    }else if(number == 2){

    }else ...

}else if(type == MetaDataConstant.TYPE_02){
    if(number == 1){

    }else if(number == 2){

    }else ...

}else if(type == MetaDataConstant.TYPE_03){
    ...
}else if(type == MetaDataConstant.TYPE_04){
    ...
}else if(type == MetaDataConstant.TYPE_05){
    ...
}else if(type == MetaDataConstant.TYPE_06){
    ...
}else if(type == MetaDataConstant.TYPE_07){
    ...
}else if(type == MetaDataConstant.TYPE_08){
    ...
}else if(type == MetaDataConstant.TYPE_09){
    ...
}else if(type == MetaDataConstant.TYPE_10){
    ...
}else{
    ...
}

这样的代码在实际开发中应该都不陌生,实际加上逻辑代码,可能这样的代码就占据了几十甚至到上百行,今后需求发生变动,比方说修改其中一个条件的逻辑代码,代码量少的情况下你可能就直接找到那个满足条件的if语句块,然后在里面改逻辑代码,代码量多的情况下,为避免改错,你可能要去对一下花括号,但是万一代码没有格式化呢,那就真的是oh shit了,我们都知道在多人开发模式中,贸然格式化代码是件很awful的事情,这意味着你的同事可能在代码合并解决冲突的时候要点到手指很累。

为了我们代码的可阅读性,当遇上多层if else嵌套时,思考一下怎么能简化他,让代码不再是大片大片的if else。

接下来我将说说实际开发中,我个人是怎么去简化的。

1 逆向思维

很多时候当程序员拿到需求时,多数情况下他会优先考虑满足条件的情况,如下:

if(condition-1){//满足条件1
    if(condition-2){//满足条件2
        if(condition-3){//满足条件3
            ....
            //业务逻辑
        }
    }
}

这种思维模式,我暂时把它叫做顺向思维,很多时候程序员更偏向于这种开发思维。但是这样的思维很多时候往往是导致if else多层嵌套的元首。

当遇到这种情况下,我就会开始逆向思考,条件不满足的情况会怎么样,其实我感觉这样的思维有时更好的把控全局展示。举个栗子,在做网络数据加载展示的时候,程序员集成接口把数据展示出来了,但最终测试过来反馈当没网络的时候页面展示是空白的,希望加载不了的时候给用户点提示。

类似这样的例子还有很多,因为大多数程序员比较关注的是成功的状态,而忽略了对失败情况的考虑。因此我认为逆向思维很多时候能避免出现考虑欠缺的情况。

示例代码:

//1--顺向思维
private void makeFile(){
    if(path != null && !"".equals(path)){//满足条件1
        File file = new File(path);
        if(!file.exists()){//满足条件2
            file.mkdirs();
        }
    }
}

//2--逆向思维
private void makeFile(){
    if(path == null || "".equals(path)){//变量值为空,不满足条件
        return;
    }
    File file = new File(path);
    if(file.exists()){//文件夹已存在,不满足创建条件
        return;
    }
    file.mkdirs();
}

上述就是很典型的逆向思维解决if else多层嵌套问题。

2 运用switch case语句

switch case这个相信不用我怎么多说大家都会知道怎么让if else转变成switch case语句,我个人并不会觉得switch case语句就比if else可阅读性高多少,只是觉得看break关键字比去对if else的花括号要简便得多,当然如果要嵌套switch case的话,那你还是使用if else好了。

示例代码:

//1--if else
if(num == 0){
    //do something...
}else if(num == 1){
    //do something...
}else if(num == 2){
    //do something...
}else{
    //do something...
}

//2--switch case
switch(num){
case 0:
    //do something...
    break;
case 1:
    //do something...
    break;
case 2:
    //do something...
    break;
default:
    //do something...
    break;
}

3 使用三目运算

首先个人是不推荐在核心、复杂的业务代码里面大量使用三目运算的,因为当业务逻辑复杂度够高时,大量的三目运算其实不便于维护人员阅读代码的,正如你们所知道的,三目运算通常是一行代码就可以搞定,所以使用三目运算的前提条件应该是业务逻辑比较单一,能让人一眼就知道这句代码的主要用来做什么。

示例代码:

//1--if else
if(isShow){
    button.setVisibility(View.VISIBLE);
}else{
    button.setVisibility(View.GONE);
}

//2--三目运算
button.setVisibility(isShow ? View.VISIBLE : View.GONE);

4 do...while

终于讲到最核心的解决方案了,就是这个do{...}while(false),正如大家知道的do while语句是一个循环体,跟while、for一样的作用,只不过do while至少会执行一次,而while、for当条件不满足时就不会执行,这几个循环体他们都是使用break关键字跳出循环,而这个break关键字就是我们最终用来解决if else多层嵌套的关键。while(false)表示循环体只执行一次。

使用do while(false)时要抽出共同代码块,与上面提到的逆向思维一致,在do语句块里面当不满足条件break,以此类推,满足往下执行,执行完do{...}while(false)后执行共同代码部分。

示例代码:

//1--if else
if(path != null && !"".equals(path)){
    File file = new File(path);
    if(file.exists()){
        boolean isSuccess = file.delete();
        if(isSuccess){
            //do something...
        }else{
            //do another thing...
        }
    }
}
//do the next thing...

//2--do while(false)
do {

    if(path == null) break;
    if("".equals(path)) break;
    File file = new File(path);
    if(!file.exists()) break;
    boolean isSuccess = file.delete();
    if(!isSuccess){
        Toast("Fail!!!");
        return;
    }
} while(false)
//do the same thing when the file is not exists...

以上就是简单举的例子。

5 责任链

接下来要提到的就是设计模式中的责任链,对于复杂的业务逻辑判断,每一个判断后要处理的内容较多时,其实可以考虑封装成一个类,以后要是条件增加或者单个逻辑处理改变了,我们只需要去增加处理类与修改对应的类,如在前言提到的那段代码。

if(type == MetaDataConstant.TYPE_01){

    if(number == 1){
        if(isShow){

        }else{

        }
    }else if(number == 2){

    }else ...

}else if(type == MetaDataConstant.TYPE_02){
    if(number == 1){

    }else if(number == 2){

    }else ...

}else if(type == MetaDataConstant.TYPE_03){
    ...
}else if(type == MetaDataConstant.TYPE_04){
    ...
}else if(type == MetaDataConstant.TYPE_05){
    ...
}else if(type == MetaDataConstant.TYPE_06){
    ...
}else if(type == MetaDataConstant.TYPE_07){
    ...
}else if(type == MetaDataConstant.TYPE_08){
    ...
}else if(type == MetaDataConstant.TYPE_09){
    ...
}else if(type == MetaDataConstant.TYPE_10){
    ...
}else{
    ...
}

当以上代码业务逻辑复杂度变大,并且需求变化较频繁,后续类型也会不断增加时,不妨考虑使用责任链解决这样的代码。

6 避免冗余代码

开发中要避免出现冗余代码。

示例代码:

//冗余代码
if(isSuccess){
    return false;
}else{
    return true;
}

//去掉冗余代码后
return !isSuccess;

终章

以上就是我个人的一些小小经验,有说的不对处,欢迎在评论中指出。

yanyan

一只正在学飞的小菜鸟