如何用最小代價重構你的"判空地獄"

在一些老項目中,咱們應該時常會碰到以下場景(若是碰不到能夠不用繼續看了):一些類的一些方法體中,有針對同一個變量進行判空的邏輯。 若是這些方法體內須要判空的方法很少,那還好說,若是一旦出現多數方法都要針對一個變量進行判空,那麼這些代碼每每就難以維護了。java

例如:git

這種大量針對同一個變量進行判空的方法,會帶來的缺點以下(仔細品味缺點是理解後續改進的重要基礎):編程

  • 多個地方重複這種判空的邏輯,會產生大量重複代碼,不易維護。
  • 若是一個類裏面大部分狀況都是這種代碼,那麼同事們一般會花更多時間來理解他們,要擴展時也會思考好久
  • 這些判空的邏輯 是沒法對 新引入的新方法進行null保護的,若是新編寫了方法,可是忘記編寫null 邏輯,那麼null 錯誤就有可能發生。

來看個具體的例子,電商項目中,咱們處理支付總會有一個統一的出入口,支付的行爲有許多種,好比常見的就是用券和不用券。數組

public class PayProcess {


    private CouponInfo couponInfo;

    public void setCouponInfo(CouponInfo couponInfo) {
        this.couponInfo = couponInfo;
    }

    //檢查支付的合法性
    public boolean checkLegitimate() {
        if (null != couponInfo) {
            //其實這裏主要就是檢查一下券有沒有過時
            return couponInfo.checkLeg();
        }
        //若是沒有券 就意味着合法
        return true;
    }

    //獲取實際支付金額
    public int getPayValue(int totalValue) {
        if (null != couponInfo) {
            //支付總金額 減去 券的金額 天然就是須要支付的金額
            return totalValue - couponInfo.getCouponValue();
        }
        return totalValue;
    }

    //獲取優惠券的類型
    public int getCouponType() {
        if (null != couponInfo) {
            return couponInfo.getCouponType();
        }
        //若是壓根就沒有優惠券  這裏就返回0 0表明沒有優惠券, 實際業務中 咱們不能寫這種魔法數字
        //必定要定義成常量,這裏爲了演示方便 我就偷懶了
        return 0;
    }


}

class CouponInfo {


    public boolean checkLeg() {
        //實際中 咱們會校驗券的時間 等等,如今爲了演示方便 我就直接返回一個false了
        //你們知道意思就好
        return false;
    }

    public int getCouponValue() {
        //返回券的實際價值,這裏也是爲了演示方便 我直接返回一個固定值
        return 3;
    }

    public int getCouponType() {
        //返回券的種類,看看是無敵券?仍是限定品類的券 等等
        //爲了演示方便 我直接寫一個int值,實際寫的時候 必定要寫成常量
        return 2;
    }
}
複製代碼

當咱們使用這個支付系統的時候,確定會有多種使用狀況,有些場景用了券,有些場景沒有用。例如:bash

public static void main(String[] args) {
        //這裏是用券的
        PayProcess p1 = new PayProcess();
        p1.setCouponInfo(new CouponInfo());

        //這裏是沒有用券的
        PayProcess p2 = new PayProcess();
        p1.setCouponInfo(null);

    }
複製代碼

肉眼可見的,咱們的PayProcess要寫不少判空的代碼。 防護式編程總沒有壞處。這也是阿里java開發手冊中提到的重要的一點,該判空的要判空,該斷定數組越界的要數組越界。思路是沒錯的,可是相似這樣的代碼,很容易就陷入了判空地獄。出現咱們文章開頭說的哪些缺點。ui

如何重構這部分老代碼?讓他看起來不是這麼糟糕?this

咱們新增一個類(其實主要目的就是在這裏統一處理爲null的狀況):spa

//這裏面的邏輯 注意看 其實和PayProcess 裏面當券爲null的時候邏輯同樣的
public class NullCouponInfo extends CouponInfo {
    public boolean checkLeg() {
        return true;
    }

    //沒有券  那券的價值就爲0
    public int getCouponValue() {
        return 0;
    }

    // 沒有券 天然type爲0
    public int getCouponType() {
        return 0;
    }
}
複製代碼

而後咱們的支付類 就能夠清爽不少:設計

public class PayProcess {


    private CouponInfo couponInfo;

    public void setCouponInfo(CouponInfo couponInfo) {
        this.couponInfo = couponInfo;
    }

    //檢查支付的合法性
    public boolean checkLegitimate() {
        //其實這裏主要就是檢查一下券有沒有過時
        return couponInfo.checkLeg();
    }

    //獲取實際支付金額
    public int getPayValue(int totalValue) {
        //支付總金額 減去 券的金額 天然就是須要支付的金額
        return totalValue - couponInfo.getCouponValue();
    }

    //獲取優惠券的類型
    public int getCouponType() {
        return couponInfo.getCouponType();
    }


}
複製代碼

最後調用的時候,當遇到券爲空的時候 就不要傳null做爲參數了指針

PayProcess p3 = new PayProcess();
   p1.setCouponInfo(new NullCouponInfo());
複製代碼

你看這樣一改完,整個邏輯上就清晰不少,可讀性也很好。也沒有那麼多重複的代碼。固然這裏還有一個隱患: 當咱們券須要新增一些方法的時候,咱們除了要改CouponInfo 還須要改NullCouponInfo,若是改漏了,那麼就會在PayProcess類中 留下隱患。雖然不會有空指針異常,可是每每不會獲得咱們想要的結果。

針對這種場景,其實咱們只要抽象出一個接口便可。讓咱們的CouponInfo和NullCouponInfo 都繼承一個接口: ICoupon(不要再讓CouponInfo做爲NullCouponInfo的父類),這樣全部新增的方法只須要在接口裏面增長便可,這樣編譯的時候就會提示咱們在2個子類中都須要實現。 從而規避掉上述的隱患。(這裏代碼比較簡單就不演示了)

總結: 將全部的null邏輯 替換爲一個null object,就能夠解決咱們文章開始時拋出的問題。有一些要點以下:

  • 若是業務邏輯簡單的時候,引入null object的模式,反而會增長代碼。因此使用者須要本身對整個業務的複雜度有必定的判斷。
  • 使用null object這種寫法,須要寫好註釋,尤爲是重構的過程當中,重構結束要通知到調用者,由於你若是引入了這種模式,而同事們都不知道,則他們可能不會爲null的狀況編寫邏輯。固然使用接口能夠規避這種狀況。
  • 即便使用接口,可是總體代碼的複雜度會略微上升。
  • 不是強校驗的null場景,一味的模仿null object 會增長設計的複雜度。
  • 不要拘泥於判空這種場景,仔細想一想其實不少時候咱們斷定一個list 是否爲空的時候 也能夠利用這種寫法。
相關文章
相關標籤/搜索