一.寫作背景
最近組內在推行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數量的檢測,哈哈。