修煉碼德系列:簡化條件表達式

 

首圖

前言

與面向過程編程相比,面向對象編程的條件表達式相對來講已經比少了,由於不少的條件行爲均可以被多態的機制處理掉;可是有時候咱們仍是會遇到一些小夥伴寫出來的條件表達式和麪向過程編程沒什麼差異,好比我遇到過這段代碼:程序員

樣例

整段代碼有三層,每一層還有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();
}
相關文章
相關標籤/搜索