領導:“這個項目,今后就給你維護了啊,仔細點。” 小貓:“好,沒問題”。 可當滿懷信心的小貓打開項目工程包翻看一些代碼之后,瞬間懵逼沒了信心。
是這樣的:
還是這樣的:
平級的if else密密麻麻就算了,但是深套五六層的if else甚至七八層的真的是讓人摸不著北。
那么就上面小貓遇到的這種情況,面對著幾代程序員精心堆積的屎山,試問閣下該如何應對?不慌,老貓羅列了以下解決方案,如果各位還有比較好的優化方法也歡迎留言。
我們對著上述目錄從簡單的開始介紹吧:
當我們遇到空對象或者有部分滿足條件之后才能執行的時候,不要只想著正向邏輯,其實可以逆向思維,把不滿足條件的優先排除掉。這樣可以有效避免if else的深嵌套。 優化前代碼:
if(condition){//doSomething}else{}return;
優化后如下:
if(!condition){ return;}
原來的代碼:
public Result addUser() { if (StrUtil.equals(userStatus, "online")) { return doStep1(); } else { return doStep2(); } // else 后面沒有其他業務時,可省略最后的else,使代碼簡潔}
優化后的代碼:
public Result addUser() { if (StrUtil.equals(userStatus, "online")) { return doStep1(); } return doStep2();}
當然這里面要注意的點是,一定要確認是最后的else,并沒有其他的業務邏輯。
還是基于上面的代碼,如果只有兩種業務的話,其實在一個方法里面直接用三目運算法進行執行即可。如下改造:
public Result addUser() { return StrUtil.equals(userStatus, "online")) ?doStep1() : doStep2();}
一個方法一行代碼搞定。
很多業務場景下,其實我們寫if 是為了判空,自從java8之后其實多了一個Optional神器,Optional 是個容器,它可以保存類型 T 的值,或者僅僅保存null。Optional 提供了很多方法,這樣我們就不用顯式進行空值檢測。Optional 類的引入很好的解決空指針異常。我們看下下面的優化方式: 代碼優化前:
if (user == null) { throw new Exception("未查詢到用戶信息");}if (user != null) { update(user); // 執行方法調用}
代碼優化后:
Optional.ofNullable(user).orElseThrow(() -> new Exception("未查詢到用戶信息"));Optional.ofNullable(user).ifPresent(user -> update(user));
隱式調用相當優雅。
設計模式優化法其實也是針對不同的場景使用不同的設計模式從而簡化多余的if else。
(1) 第一種,合理使用責任鏈模式。
我們再具體結合一種場景,比方說現在頁面上有新注冊的用戶,他需要提交相關的身份信息進行認證,此時,我們底層往往會對他提交的信息做相關的校驗處理。 底層我們的校驗方式(1)需要驗證基本字非空性 (2)需要驗證身份信息基礎字段合法性 (2)需要調用第三方進行要素認證。 原始代碼如下:
public void addUser(User user) { // 1.非空校驗 if (StrUtil.isBlank(user.getUsername())) { throw new RuntimeException("用戶名為空!"); } if (StrUtil.isBlank(user.getPassword())) { throw new RuntimeException("密碼為空!"); } ... // 2.格式校驗 if (!ValidUtil.isIdCardNo(user.getIdCardNo())) { throw new RuntimeException("身份證號格式錯誤!"); } if (!ValidUtil.isEmail(user.getEmail())) { throw new RuntimeException("手機號格式錯誤!"); } if (!ValidUtil.isEmail(user.getEmail())) { throw new RuntimeException("郵箱格式錯誤!"); } ... // 3.要四素認證校驗 if(!doFourStampVerify(User user)){ throw new RuntimeException("四要素認證失敗!"); }}
此處可能還有很多其他的省略的場景。所以單個文件中的If else可能比想象中多的多。那么我們如何用責任鏈模式進行優化呢? 改造代碼如下,首先定義一個處理器接口:
/** * 處理器鏈接口 */public interface UserChainHandler { void handler(User user);}
剩下不同的場景校驗只要去實現這個接口就可以了,不過需要定義好順序
@Component@Order(1) // 指定注入順序public class UserParamNullValidChainHandler implements UserChainHandler { @Override public void handler(User user) { // 1.非空校驗 if (StrUtil.isBlank(user.getUsername())) { throw new RuntimeException("用戶名為空!"); } if (StrUtil.isBlank(user.getPassword())) { throw new RuntimeException("密碼為空!"); }}@Component@Order(1) // 指定注入順序public class UserParamNullValidChainHandler implements UserChainHandler { @Override public void handler(User user) { // 1.非空校驗 if (StrUtil.isBlank(user.getUsername())) { throw new RuntimeException("用戶名為空!"); } ...}/** * 格式校驗處理器 */@Component@Order(2) // 指定注入順序public class UserParamFormatValidChainHandler implements UserChainHandler { @Override public void handler(User user) { // 2.格式校驗 if (!ValidUtil.isIdCardNo(user.getIdCardNo())) { throw new RuntimeException("身份證號格式錯誤!"); } ...}/** * 四要素處理器 */@Component@Order(3) // 指定注入順序public class FourElementVerifyChainHandler implements UserChainHandler { @Override public void handler(User user) { // 2.格式校驗 if (!doFourStampVerify(User user)) { throw new RuntimeException("四要素認證失敗!"); }}
//進行組裝@Component@RequiredArgsConstructorpublic class UserChainContext { private final List<UserChainHandler> userChainHandlerList; // 自動注入責任鏈處理器 /** * 責任鏈組件執行 * * @param requestParam 請求參數 */ public void handler(User user) { // 此處根據 Ordered 實際值進行排序處理 userChainHandlerList.forEach(x -> x.handler(user)); }}
最終咱們的原來的add方法進行這樣調用就好了:
public void addUser(User user) { // 執行責任鏈 userChainContext.handler(user);}
(2) 第二種,合理使用策略模式+工廠模式。
假設我們遇到這樣一個場景,我們目前底層是一個會員系統,目前系統需要計算各種會員套餐的價格,然后套餐的具體模式主要是由上層系統傳遞指定給我們。如果只關注業務直接擼代碼的話,應該是如下。
public Result calcPrice(CalcPriceParam calcPriceParam){ //判斷對應的計算價格的場景 Integer type = judgeType(calcPriceParam); //根據場景調用不同的方法 ,建議更好的編碼習慣是把type改成枚舉類型哈~ if(type == 1){ return calcPriceForTypeOne(); } if(type == 2){ return calcPriceForTypeTwo(); } if(type == 3){ return calcPriceForTypeThree(); } ..... if(typr == 10){ return calcPriceForTypeTen(); }}
顯而易見隨著會員價格場景套餐越來越多,我們的if也會越來越多。 但是如果使用策略模式的話,我們可以做到如下:
public interface Strategy { Result calcPrice(CalcPriceParam calcPriceParam); int getBizType();}@Servicepublic Class firstStragy implement Strategy { Result calcPrice(CalcPriceParam calcPriceParam) { .... return result; } int getBizType() { return 1; }}public Class secondStragy implement Strategy { Result calcPrice(CalcPriceParam calcPriceParam) { .... return result; } int getBizType() { return 2; }}@Servicepublic class StrategyContext{ Map<Integer,CalcPriceInterface> strategyContextMap = new HashMap<>(); //注入對應的策略類 @Autowired Strategy[] strategys; @PostConstruct public void setStrategyContextMap(){ for(Stragegy stragegy:strategys){ strategyContextMap.put(stragegy.getCode,stragegy); } } //根據場景調用不同的方法 public Result calcPrice(CalcPriceParam calcPriceParam){ Integer type = judgeType(calcPriceParam); CalcPriceInterface calcPriceInstance = strategyContextMap.get(type); return calcPriceInstance.calcPrice(calcPriceParam); }}
這樣一來,咱們上面的第一個方法中的If else的實現將會變得很簡單,如下:
@AutowiredStrategyContext strategyContext;public Result calcPrice(CalcPriceParam calcPriceParam){ strategyContext.calcPrice(calcPriceParam);}
這樣即使新增新的計算模式,我們只需去實現Strategy接口并且重寫里面兩個方法即可完成后續業務的拓展。代碼優雅簡單,可維護性強。 以上就是用設計模式針對大量if else進行改造。
這種方式個人覺得有點像策略模式,但是又不需要單獨抽出相關類去承載注冊方法,而是簡單地將方法通過函數式的方式放到Map中,等到需要使用的時候再進行調用。 原始爛代碼,我們還是參考上述會員費用金額計算的場景。我們可以進行如下方式優化:
Map<String, Function<?> action> actionMap = new HashMap<>();action.put("type1",() -> {calcPriceForTypeOne()});action.put("type2",() -> {calcPriceForTypeTwo()});action.put("type3",() -> {calcPriceForTypeThree()});...// 使用actionMap.get(action).apply();
當然如果想要再優化得好一些的話,可以進行接口抽取,然后進行實現,在此不展開,留下給小伙伴們思考一下。
我們再回到之前小貓遇到的那兩個代碼截圖,其實我們可以看到有個大量if else并排的代碼其實主要是想要比較相關的屬性有沒有發生變化,如果發生變化,那么則返回false,沒有變化則返回true。其實我們想想是不是可以通過重寫LogisticDO這個對象的equals方法來進行實現呢?這樣是不是也規避了大量的if else。
還有其他一些當然也是根據具體場景來解決,比方說,我需要根據不同的type類型,進行獲取不同的描述信息,那么此時我們是不是可以使用enum去維護呢? 如下:
if(status.equals(1)){ return "訂單未支付";}else if(status.equals(2)){ return "訂單已支付"}else if(status.equals(3)){ return "訂單已發貨"}.....
優化后:
@Getter@AllArgsConstructorpublic enum OrderStatusEnum { UN_PAID("1","訂單未支付"), PAIDED("2","訂單已支付"), SENDED("3","訂單已發貨"), .....; private String status; private String statusDes; static OrderStatusEnum of(String status) { for (OrderStatusEnum statusEnum : OrderStatusEnum.values()) { if (statusEnum.getStatus().equals(status)) { return statusEnum; } } return null; }}String orderStatusDes = OrderStatusEnum.of(orderStatus).getStatusDes();
等等還有其他一些,由于這些優化個人認為是沒法標準化的優化原則,不同的業務場景都不同,所以在此,老貓不將其放在通用優化中,認為這個是其他優化方式。
之前在某個技術論壇上看到大家在爭論這么一個問題“如何避免將維護的項目發展成屎山?”大家發言踴躍。有說前期做好設計,有人說代碼質量需要高一些,合理場景套用一些設計模式等等。 不過老貓認為項目無法避免發展成屎山,只是快慢而已,我也認為項目無法避免發展成“屎山”。其原因有三點:
說了這么多,其實老貓最終想表達的是,雖然項目會最終淪為屎山,但是作為一個有追求的研發,我們就應當從每個小的if else著手,至少讓當前這個項目在你維護期間,讓其發展成屎山的速度變慢一些,或者能替之前的老前輩還掉一些技術債才是最好的,各位小伙伴你們覺得呢?
本文鏈接:http://www.www897cc.com/showinfo-26-60958-0.html接手了個項目,被if..else搞懵逼了
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: 終究還是拿下字節!強度拉滿!
下一篇: Java 新技術:虛擬線程使用指南