論減少代碼中return語句的騷操作


一.寫作背景

最近組內在推行checkstyle代碼規范的檢測,關於checkstyle的介紹可以參考:https://checkstyle.sourceforge.io,

在按照checkstyle修改問題時,遇到幾個很頭疼的問題,最頭疼就是checkstyle對function中return數量的限制,這里有兩種限制:

  1.對於void返回值的function,return數量最多只允許有1個;

  2.對於非void返回值的function,return數量最多只允許個有3個;

根據上面這兩個限制,在修改代碼的過程中,真的是很難受,按照這個規則來寫代碼,會讓代碼變得更難理解(和人的思維方式有區別),所以我下面會簡單介紹一下為啥會很頭疼,然后介紹相關的騷操作,用來減少return的數量。

原文地址:https://www.cnblogs.com/-beyond/p/13831474.html

 

二.多個return的實例

通常項目中很多的service中都會進行組裝流程,一個function中會包含多個步驟,每個步驟都會有階段性的處理結果,並且需要根據階段性的結果進行判斷是否需要繼續執行;如果不繼續執行,那么就需要返回對應的錯誤碼給上層。

比如下面這樣例子

package cn.ganlixin;

/**
 * @author ganlixin
 * @create 2020-10-17
 */
public class CookService {

    private WaterManager waterManager;
    private VegetableManager vegetableManager;
    private FireManager fireManager;
    private RiceManager riceManager;
    
    public Result cook(Param param) {
        log.info("start cook check, param:{}", param);

        Result fireResult = fireManager.getFire(param);
        if (fireResult != Result.SUCCESS) {
            log.error("cook failed, no fire, because {}", fireResult);
            return fireResult;
        }

        Result waterResult = waterManager.getWater(param);
        if (waterResult != Result.SUCCESS) {
            log.error("cook failed, no water, because {}", waterResult);
            return waterResult;
        }

        Result riceResult = riceManager.getRice(param);
        if (riceResult != Result.SUCCESS) {
            log.error("cook failed, no rice, because {}", riceResult);
            return riceResult;
        }

        Result vegetableResult = vegetableManager.getVegetables(param);
        if (vegetableResult != Result.SUCCESS) {
            log.error("cook failed, no vegetables, because {}", vegetableResult);
            return vegetableResult;
        }

        log.info("steps checked pass success, start to do cook");
        return doCook(param);
    }
}

上面的例子中,return的數量已經由5個了,其實這個還不算多,有的大流程包含10多個return,修改這樣的代碼,就挺煩的。

對於上面這個代碼塊,說一下我的思路:

  1.上面的代碼,每一步操作結果進行判斷,如果不符合要求,及時終止流程,符合正常的思維;

  2.也是有可以優化的地方:return數量過多,在一定的程度上,可以認為是封裝做得不好,或者說沒有按照步驟的相關性單拎出來封裝;比如說上面的代碼中,可以將獲取水、米、蔬菜的三個步驟提出來,封裝一個獲取食材的方法,在提出來的方法中再進行判斷,這樣return的數量減少了一些,同時代碼也更加好理解。

 

三.常見的return優化方式

  下面介紹幾種修改代碼的方式,這些方式在初期接入checkstyle的時候,我就是按照這種方式來修改的,但是我並不推薦使用

3.1 合理使用三元表達式

修改前的代碼

public Result case1(Param param) {
    Integer code= doSomeAction(param);
    if (code == 0) {
        return Result.SUCCESS;
    } else {
        return Result.FAIL;
    }
}

  使用三元表達式來修改,看起來更加清爽了,如下:

public Result case1(Param param) {
    Integer code = doSomeAction(param);
    return code == 0 ? Result.SUCCESS : Result.FAIL;
}

  

3.2 合並相關操作

就像前面說的,將獲取水、米、蔬菜的三個步驟封裝為一個大的步驟(獲取食材);

除此之外,下面還有一個例子,case2接收入參后進行了兩次參數合法性檢測,其實這兩次合法性檢測是可以合並的:

public Result case2(Param param) {
    log.info("input param:{}", param);
    if (param == null) {
        log.info("param error");
        return Result.PARAM_ERROR;
    }

    if (!checkParamValid(param)) {
        log.info("param error");
        return Result.PARAM_ERROR;
    }

    Integer code = doSomeAction(param);
    return code == 0 ? Result.SUCCESS : Result.FAIL;
}

  將兩次檢測合並,修改后的代碼

public Result case2(Param param) {
    log.info("input param:{}", param);
    if (param == null || !checkParamValid(param)) {
        log.info("param error");
        return Result.PARAM_ERROR;
    }

    Integer code = doSomeAction(param);
    return code == 0 ? Result.SUCCESS : Result.FAIL;
}

  點評:

  1.將相關的操作進行封裝、合並,不僅會讓代碼更簡潔,閱讀代碼也會更好理解;

  2.上面雖然將檢測操作進行了簡單的合並,但是也存在一些問題,如果檢測參數的時候,需要返回那個字段或者屬性不合法,那么上面的方式也就不推薦了。

 

3.3 使用變量保存if.else.的返回值

直接看下面的代碼:

public Result case3(Param param) {
    log.info("input param:{}", param);
    // 省略參數校驗
    
    if (param.role == "admin") {
        // ...
        
        return adminHandle(param);
    } else {
        // ..
        
        return userHandle(param);
    }
}

  使用變量保存分支的處理結果,然后統一返回

public Result case3(Param param) {
    log.info("input param:{}", param);
    // 省略參數校驗

    Result res = Result.FAIL;
    if (param.role == "admin") {
        // ...

        res = adminHandle(param);
    } else {
        // ..

        res = userHandle(param);
    }
    
    return res;
}

  點評:

  其實這種方式並不推薦,如果分支較少,並且每個分支的操作只有幾行代碼,這樣修改也沒啥關系;

  但是如果分支很多,並且每個分支的代碼量很大,那么不論是寫代碼或者看代碼,亦或者是后面維護代碼,都需要小心res變量被修改錯了或者忘記修改。(題外話:如果分支代碼量太大,可以將分支中的操作封裝一下)。

  

3.4 利用if的逆向邏輯

看一下下面代碼:

public Result case4(Param param) {
    log.info("input param:{}", param);
    if (!checkParam()) {
        log.info("參數錯誤");
        return Result.PARAM_ERROR;
    }

    if (!checkPermission(param.getUserId)) {
        log.info("沒有權限");
        return Result.PERMISSION_DENY;
    }

    return doAction(param);
}

  要想減少上面代碼return,的確是有方法,修改之后如下:

public Result case4(Param param) {
    log.info("input param:{}", param);
    Result result = Result.FAIL;
    if (checkParam()) {
        if (checkPermission(param.getUserId)) {
            return doAction(param);
        }
        
        log.info("沒有權限");
        Result.PERMISSION_DENY;
    } else {
        
        log.info("參數錯誤");
        result = Result.PARAM_ERROR;
    }

    return result;
}

  點評:

  上面這種寫法,有的人看着感覺沒啥,有的人看着就挺別扭。

  按照修改之前的寫法,當發現參數錯誤是,立馬返回結果,也就瀏覽4行代碼;如果按照修改之后的代碼,校驗參數錯誤到返回結果,需要看15行左右(這只是一個簡單地示例,正常業務流程中包含的代碼會更多)。

 

四.總結

這篇博客,不是真的想舉例子講怎么去減少return數量,我覺得return數量多點也沒關系,及時return也就及時結束,比較符合人的思維方式。

雖然可以使用各種騷操作去減少return的數量,比如使用if..else..去搞也是可以的,但是這樣的話,一方面,從閱讀代碼的角度,流程更復雜了;另一方面,使用if..else去搞多層嵌套,每行的代碼長度就會很多,閱讀代碼也不方便。

寫這篇博客,籠統點說就是:

  1.做事情要多總結,映射到代碼上就是多提煉、多抽象、多封裝;

  2.不要為了規范而放棄一下東西,映射到代碼上,就是代碼可讀性、代碼簡潔性很重要;

如果通不過規范,那么可以找上級多溝通溝通,如果理由正當且合理,那么規范也是可以跳過的,比如使用@SuppressWarnings("ReturnCount")來忽略return數量的檢測,哈哈。

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM