乐趣区

关于java:修炼码德系列简化条件表达式

前言

与面向过程编程相比,面向对象编程的条件表达式相对来说曾经比少了,因为很多的条件行为都能够被多态的机制解决掉;然而有时候咱们还是会遇到一些小伙伴写进去的条件表达式和面向过程编程没什么差异,比方我遇到过这段代码:

整段代码有三层,每一层还有 if-else,自身的这段代码的逻辑就够难以了解了,更加恶心的是这个办法的调用方以及调用的其余办法,同样也是这样的 if-else 嵌套几层;
加之这段代码还有一个很大的问题是传入的参数对象,在外部以及调用的其余办法中被批改屡次批改,这样就更难懂了;靠普通人的单核 CPU 想看懂太难了,保护这段代码我感觉身材被掏空

有时候咱们可能会遇到比较复杂的条件逻辑,须要咱们想方法把分成若干个小块,让分支逻辑和操作细节拆散;看一个程序员的码德如何,先看他的条件表达式是否够简洁易懂;明天咱们来分享一下简化条件表达式的罕用办法,修炼本人的码德;本文中大部分的例子来源于《重构改善既有代码设计》


合成条件表达式

简单的条件逻辑是最常导致复杂度回升的中央之一,另外如果分支外部逻辑也很多,最终咱们会失去一个很大的函数,一个长的办法可读性自身就会降落,所以咱们须要把大的办法才分的多个的办法,为每个办法取一个容易分明表白实现外部逻辑的办法名,这样可读性就会上大大提高。

举例:

if (date.before (SUMMER_START) || date.after(SUMMER_END)) {charge = quantity * _winterRate + _winterServiceCharge;} else {charge = quantity * _summerRate}

这种代码很多人可能都感觉没必要去提取办法,然而如果咱们想要看懂这段代码,还是必须的去想想才晓得在做什么;接下来咱们批改一下

if (notSummer(date)) {charge = winterCharge(quantity);
} else {charge = summerCharge(quantity);
}

private boolean notSummer(Date date){date.before (SUMMER_START) || date.after(SUMMER_END)
}

private double summerCharge(int quantity) {return quantity * _summerRate;}

private double winterCharge(int quantity) {return quantity * _winterRate + _winterServiceCharge;}

这样批改之后是不是很分明,好的代码自身不须要写正文(代码具备自说明性),更不须要在办法外部写任何正文,有时候咱们会看到有同学会在办法外部隔几行就会写一点正文,这阐明自身代码的自说明性不够好,能够通过方才这个例子的形式进步代码的可读性


合并条件表达式

当遇到一段代码多个 if 条件判断,然而条件外部的逻辑缺相似,咱们能够把条件合并在一起,而后抽取办法。

举例 1:

double disabilityAmount () {if(_seniortiy <2) 
        return 0;
    if(_monthsDisabled > 12)
        return 0;
    if(_isPartTime)
        return 0;
    // 省略...
}

这里的条件返回的后果都是一样的,那么咱们先把条件合并起来

double disabilityAmount () {if(_seniortiy <2 || _monthsDisabled > 12 || _isPartTime) {return 0;}
    // 省略...
}

接下来咱们再来把判断条件判断条件抽取成办法进步可读性

double disabilityAmount () {if(isNotEligibleForDisableility()) {return 0;}
    // 省略...
}

boolean isNotEligibleForDisableility() {return _seniortiy <2 || _monthsDisabled > 12 || _isPartTime;}

举例 2:

if(onVacation()) {if(lengthOfService() > 10) {return 2;}
}
return 1;

合并之后的代码

if(onVacation() && lengthOfService() > 10){return 2}
return 1;

接着咱们能够应用三元操作符更加简化,批改后的代码:

return onVacation() && lengthOfService() > 10 ? 2 : 1;

通过这两个例子咱们能够看出,先把条件逻辑与分支逻辑抽离成不同的办法分来到,而后咱们会发现进步代码的可读性是如此的简略,得心应手;所以抽离好的办法是要害;我感觉此处应该有掌声


合并反复的条件片段

咱们先来看一个例子,10 岁以下的小朋友票价打 5 折

if(ageLt10(age)) {
    price = price * 0.5;
    doSomething();} else {
    price = price * 1;
    doSomething();}

咱们发现不同的分支都执行了雷同的末段代码逻辑,这时候咱们能够把这段代码提取到条件判断之外,这里举得例子较为简单,通常工作中遇到的可能不是这样一个简略的办法,而是很多行简单的逻辑条件,咱们能够先把这个代码提取成一个办法,而后把这个办法的调用放在条件判断之前或之后

批改之后的代码

if(ageLt10(age)) {price = price * 0.5;} else {price = price * 1;}
doSomething();

当咱们遇到 try-catch 中有雷同的逻辑代码,咱们也能够应用这种形式解决


卫语句取代嵌套条件表达式

办法中一旦呈现很深的嵌套逻辑让人很难看懂执行的主线。当应用了 if-else 示意两个分支都是等同的重要,都是主线流程;向下图表白的一样,

然而大多数时候咱们会遇到只有一条主线流程,其余的都是个别的异常情况,在这种状况下应用 if-else 就不太适合,应该用卫语句取代嵌套表达式。

举例 1:

在薪酬零碎中,以非凡的规定解决死亡员工,驻外员工,退休员工的薪资,这些状况都是很少呈现,不属于失常逻辑;

double getPayAmount() {
    double result;
    if(isDead){result = deadAmount();
    } else {if(isSeparated) {result = separatedAmount();
        } else {if(isRetired) {result = retiredAmount();
            } else {result = normalPayAmount();
            }
        }
    }
    return result;
}

在这段代码中,咱们齐全看不出失常流程是什么,这些偶然产生的状况把失常流程给覆盖了,一旦产生了偶尔状况,就应该间接返回,疏导代码的维护者去看一个没用意义的 else 只会障碍了解;让咱们用 return 来革新一下

double getPayAmount() {if(isDead){return deadAmount();
    }
    if(isSeparated) {return separatedAmount():
    }
    if(isRetired) {return retiredAmount();
    }
    return normalPayAmount();}

多态取代条件表达式

有时候咱们会遇到 if-else-if 或者 switch-case 这种构造,这样的代码不仅不够整洁,遇到简单逻辑也同样难以了解。这种状况咱们能够利用面向对象的多态来革新。

举例:
如果你正在开发一款游戏,须要写一个获取箭塔 (Bartizan)、弓箭手(Archer)、坦克(Tank) 攻击力的办法;通过两个小时的致力终于实现了这个性能;开发实现后的代码如下:

int attackPower(Attacker attacker) {switch (attacker.getType()) {
        case "Bartizan":
            return 100;
        case "Archer":
            return 50;
        case "Tank":
            return 800;
    }
    throw new RuntimeException("can not support the method");
}

通过自测后没有任何问题,此时你的情绪很爽

当你提交代码交由领导 review 的时候,领导(心里想着这点货色搞两个小时,下班摸鱼太显著了吧)间接回复代码实现不够优雅,重写

1. 枚举多态

你看到这个回复尽管心里很不爽,然而你也没方法,毕竟还是要在这里混饭吃的;嘴上还是的答复 OK

你思考了一会想到了应用枚举的多态来实现不就行了,说干就干,于是你写了下一个版本

int attackPower(Attacker attacker) {return AttackerType.valueOf(attacker.getType()).getAttackPower();}

enum AttackerType {Bartizan("箭塔") {
        @Override
        public int getAttackPower() {return 100;}
    },
    Archer("弓箭手") {
        @Override
        public int getAttackPower() {return 50;}
    },
    Tank("坦克") {
        @Override
        public int getAttackPower() {return 800;}
    };

    private String label;

    Attacker(String label) {this.label = label;}

    public String getLabel() {return label;}

    public int getAttackPower() {throw new RuntimeException("Can not support the method");
    }
}

这次再提交领导 review,顺利通过了,你心想总于 get 到了领导的点了

2. 类多态

没想到你没快乐几天,又接到个新的需要,这个获取攻击力的办法须要批改,依据攻击者的等级不同而攻击力也不同;你思考到上次的版本攻击力是固定的值,应用枚举还比拟适合,而这次的批改要依据攻击者的自身等级来计算攻打了,如果再应用枚举预计是不适合的;同时你也想着上次简略实现被领导怼了,这次如果还是在上次的枚举版本上来实现,预计也不会有好后果;最初你决定应用类的多态来实现

int attackPower(Attacker attacker) {return attacker.getAttackPower();
}

interface Attacker {default int getAttackPower() {throw new RuntimeException("Can not support the method");
    }
}

class Bartizan implements Attacker {public int getAttackPower() {return 100 * getLevel();
    }
}

class Archer implements Attacker {public int getAttackPower() {return 50 * getLevel();
    }
}

class Tank implements Attacker {public int getAttackPower() {return 800 * getLevel();
    }
}

实现之后提交给领导 review,领导笑了笑通过了代码评审;

3. 策略模式

你本认为这样就完结了,后果打算赶不上变动,游戏上线后,成果不是太好,你又接到了一个需要变更,攻击力的计算不能这么粗犷,咱们须要后盾配置规定,让局部加入流动玩家的攻击力依据规定晋升。

你很怄气,心里想着: 没听说过杀死程序员不须要用枪吗,改三次需要就能够了,MD 这是想我死吗。

怄气归怄气,然而不敢表露进去,谁让你是领导呢,那就开搞吧

思考到本次的逻辑退出了规定,规定自身能够设计的简略,也能够设计的很简单,如果前期规定变得更加简单,那么整个攻打对象类中会显得特地的臃肿,扩展性也不好,所以你这次不再应用类的多态来实现,思考应用策略模式,实现后代码如下:

// 定义计算类的接口
interface AttackPowerCalculator {boolean support(Attacker attacker);

    int calculate(Attacker attacker);
}

// 箭塔攻击力计算类
class BartizanAttackPowerCalculator implements AttackPowerCalculator {

    @Override
    public boolean support(Attacker attacker) {return "Bartizan".equals(attacker.getType());
    }

    @Override
    public int calculate(Attacker attacker) {
        // 依据规定计算攻击力
        return doCalculate(getRule());
    }
}

// 弓箭手攻击力计算类
class ArcherAttackPowerCalculator implements AttackPowerCalculator {

    @Override
    public boolean support(Attacker attacker) {return "Archer".equals(attacker.getType());
    }

    @Override
    public int calculate(Attacker attacker) {
        // 依据规定计算攻击力
        return doCalculate(getRule());
    }
}

// 坦克攻击力计算类
class TankAttackPowerCalculator implements AttackPowerCalculator {

    @Override
    public boolean support(Attacker attacker) {return "Tank".equals(attacker.getType());
    }

    @Override
    public int calculate(Attacker attacker) {
        // 依据规定计算攻击力
        return doCalculate(getRule());
    }
}

// 聚合所有计算类
class AttackPowerCalculatorComposite implements AttackPowerCalculator {List<AttackPowerCalculator> calculators = new ArrayList<>();

    public AttackPowerCalculatorComposite() {this.calculators.add(new TankAttackPowerCalculator());
        this.calculators.add(new ArcherAttackPowerCalculator());
        this.calculators.add(new BartizanAttackPowerCalculator());
    }

    @Override
    public boolean support(Attacker attacker) {return true;}

    @Override
    public int calculate(Attacker attacker) {for (AttackPowerCalculator calculator : calculators) {if (calculator.support(attacker)) {calculator.calculate(attacker);
            }
        }
        throw new RuntimeException("Can not support the method"); 
    }
}

// 入口处通过调用聚合类来实现计算
int attackPower(Attacker attacker) {AttackPowerCalculator calculator = new AttackPowerCalculatorComposite();
    return calculator.calculate(attacker);
}

你再次提交代码给领导 review,领导看了很称心,褒扬你说:小伙子,不错,提高很快嘛,给你点赞哦;你答复:感激领导认可(心里想那是当然,毕竟我曾经摸清了你的点在哪里了)

感觉本次你的这个性能实现的还比较满意的,请点赞关注评论走起哦

引入断言

最初一个简化条件表达式的操作是引入断言,这部分比较简单,并且 Spring 框架自身也提供了断言的工具类,比方上面这段代码:

public void getProjectLimit(String project){if(project == null){throw new RuntimeException("project can not null");
    }
    doSomething();}

退出 Spring 的断言后的代码

public void getProjectLimit(String project){Assert.notNull(project,"project can not null");
    doSomething();}

写在最初
感激大家能够急躁地读到这里。
当然,文中或者会存在或多或少的有余、谬误之处,有倡议或者意见也十分欢送大家在评论交换。
最初,心愿敌人们能够点赞评论关注三连,因为这些就是我分享的全副能源起源????

往期 Java8 系列文章:

  • CompletableFuture 让你的代码免受阻塞之苦
  • 如何高效的应用并行流
  • Java 中 NullPointerException 的完满解决方案
  • 面试者必看:Java8 中的默认办法
  • Java8 为什么须要引入新的日期和工夫库
退出移动版