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

 

首图

前言

与面向过程编程相比,面向对象编程的条件表达式相对来讲已经比少了,由于不少的条件行为均可以被多态的机制处理掉;可是有时候咱们仍是会遇到一些小伙伴写出来的条件表达式和面向过程编程没什么差异,好比我遇到过这段代码:程序员

样例

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

身体被掏空

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


分解条件表达式

复杂的条件逻辑是最常致使复杂度上升的地方之一,另外若是分支内部逻辑也不少,最终咱们会获得一个很大的函数,一个长的方法可读性自己就会降低,因此咱们须要把大的方法才分的多个的方法,为每一个方法取一个容易清楚表达实现内部逻辑的方法名,这样可读性就会上大大提升。ide

举例:函数

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;
}

这样修改以后是否是很清楚,好的代码自己不须要写注释(代码具备自说明性),更不须要在方法内部写任何注释,有时候咱们会看到有同窗会在方法内部隔几行就会写一点注释,这说明自己代码的自说明性不够好,能够经过刚才这个例子的方式提升代码的可读性this


合并条件表达式

当遇到一段代码多个if条件判断,可是条件内部的逻辑缺相似,咱们能够把条件合并在一块儿,而后抽取方法。设计

举例1:orm

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

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

举例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();
}
相关文章
相关标签/搜索