實戰(zhàn)技巧:if-else代碼優(yōu)化技巧(if else如何優(yōu)化)
在實際的業(yè)務(wù)開發(fā)當(dāng)中,經(jīng)常會遇到復(fù)雜的業(yè)務(wù)邏輯,可能部分同學(xué)實現(xiàn)出來的代碼并沒有什么問題,但是代碼的可讀性很差。本篇文章主要總結(jié)一下自己在實際開發(fā)中如何避免大面積的 if-else 代碼塊的問題。補(bǔ)充說明一點,不是說 if-else 不好,而是多層嵌套的 if-else 導(dǎo)致代碼可讀性差、維護(hù)成本高等問題。
??現(xiàn)有如下一段示例代碼,部分優(yōu)化技巧是根據(jù)這段代碼進(jìn)行的
public class BadCodeDemo { private void getBadCodeBiz(Integer city, List<TestCodeData> newDataList, List<TestCodeData> oldDataList) { if (city != null) { if (newDataList != null && newDataList.size() > 0) { TestCodeData newData = newDataList.stream().filter(p -> { if (p.getIsHoliday() == 1) { return true; } return false; }).findFirst().orElse(null); if (newData != null) { newData.setCity(city); } } } else { if (oldDataList != null && newDataList != null) { List<TestCodeData> oldCollect = oldDataList.stream().filter(p -> { if (p.getIsHoliday() == 1) { return true; } return false; }).collect(Collectors.toList()); List<TestCodeData> newCollect = newDataList.stream().filter(p -> { if (p.getIsHoliday() == 1) { return true; } return false; }).collect(Collectors.toList()); if (newCollect != null && newCollect.size() > 0 && oldCollect != null && oldCollect.size() > 0) { for (TestCodeData newPO : newCollect) { if (newPO.getStartTime() == 0 && newPO.getEndTime() == 12) { TestCodeData po = oldCollect.stream().filter(p -> p.getStartTime() == 0 && (p.getEndTime() == 12 || p.getEndTime() == 24)).findFirst().orElse(null); if (po != null) { newPO.setCity(po.getCity()); } } else if (newPO.getStartTime() == 12 && newPO.getEndTime() == 24) { TestCodeData po = oldCollect.stream().filter( p -> (p.getStartTime() == 12 || p.getStartTime() == 0) && p.getEndTime() == 24).findFirst().orElse(null); if (po != null) { newPO.setCity(po.getCity()); } } else if (newPO.getStartTime() == 0 && newPO.getEndTime() == 24) { TestCodeData po = oldCollect.stream().filter( p -> p.getStartTime() == 0 && p.getEndTime() == 24).findFirst().orElse(null); if (po == null) { po = oldCollect.stream().filter( p -> p.getStartTime() == 0 && p.getEndTime() == 12).findFirst().orElse(null); } if (po == null) { po = oldCollect.stream().filter( p -> p.getStartTime() == 12 && p.getEndTime() == 24).findFirst().orElse(null); } if (po != null) { newPO.setCity(po.getCity()); } } else if (newPO.getTimeUnit().equals(Integer.valueOf(1))) { TestCodeData po = oldCollect.stream().filter( e -> e.getTimeUnit().equals(Integer.valueOf(1))).findFirst().orElse(null); if (po != null) { newPO.setCity(po.getCity()); } } } } } } }}復(fù)制代碼
技巧一:提取方法,拆分邏輯
比如上面這段代碼中
if(null != city) {} else {}復(fù)制代碼
這里可以拆分成兩段邏輯,核心思想就是邏輯單元最小化,然后合并邏輯單元。
private void getCityNotNull(Integer city, List<TestCodeData> newDataList) { if (newDataList != null && newDataList.size() > 0) { TestCodeData newData = newDataList.stream().filter(p -> { if (p.getIsHoliday() == 1) { return true; } return false; }).findFirst().orElse(null); if (newData != null) { newData.setCity(city); } }}// 合并邏輯流程private void getBadCodeBiz(Integer city, List<TestCodeData> newDataList, List<TestCodeData> oldDataList) { if (city != null) { this.getCityNull(city, newDataList); } else { //此處代碼省略 }}復(fù)制代碼
技巧二:分支邏輯提前return
比如 技巧一 中的 getCityNull 方法,我們可以這樣寫
public void getCityNotNull(Integer city, List<TestCodeData> newDataList) { if (CollectionUtils.isEmpty(newDataList)) { // 提前判斷,返回業(yè)務(wù)邏輯 return; } TestCodeData newData = newDataList.stream().filter(p -> { if (p.getIsHoliday() == 1) { return true; } return false; }).findFirst().orElse(null); if (null != newData) { newData.setCity(city); }}復(fù)制代碼
技巧三:枚舉
經(jīng)過 技巧一 和 技巧二 的優(yōu)化,文章開頭的這段代碼被優(yōu)化成如下所示:
public class BadCodeDemo { public void getBadCodeBiz(Integer city, List<TestCodeData> newDataList, List<TestCodeData> oldDataList) { if (city != null) { this.getCityNotNull(city, newDataList); } else { this.getCityNull(newDataList, oldDataList); } } private void getCityNotNull(Integer city, List<TestCodeData> newDataList) { if (CollectionUtils.isEmpty(newDataList)) { // 提前判斷,返回業(yè)務(wù)邏輯 return; } TestCodeData newData = newDataList.stream().filter(p -> { if (p.getIsHoliday() == 1) { return true; } return false; }).findFirst().orElse(null); if (null != newData) { newData.setCity(city); } } private void getCityNull(List<TestCodeData> newDataList, List<TestCodeData> oldDataList) { // 提前判斷,返回業(yè)務(wù)邏輯 if (CollectionUtils.isEmpty(oldDataList) && CollectionUtils.isEmpty(newDataList)) { return; } List<TestCodeData> oldCollect = oldDataList.stream().filter(p -> { if (p.getIsHoliday() == 1) { return true; } return false; }).collect(Collectors.toList()); List<TestCodeData> newCollect = newDataList.stream().filter(p -> { if (p.getIsHoliday() == 1) { return true; } return false; }).collect(Collectors.toList()); // 提前判斷,返回業(yè)務(wù)邏輯 if (CollectionUtils.isEmpty(newCollect) && CollectionUtils.isEmpty(oldCollect)) { return; } for (TestCodeData newPO : newCollect) { if (newPO.getStartTime() == 0 && newPO.getEndTime() == 12) { TestCodeData po = oldCollect.stream().filter(p -> p.getStartTime() == 0 && (p.getEndTime() == 12 || p.getEndTime() == 24)).findFirst().orElse(null); if (po != null) { newPO.setCity(po.getCity()); } } else if (newPO.getStartTime() == 12 && newPO.getEndTime() == 24) { TestCodeData po = oldCollect.stream().filter( p -> (p.getStartTime() == 12 || p.getStartTime() == 0) && p.getEndTime() == 24).findFirst().orElse(null); if (po != null) { newPO.setCity(po.getCity()); } } else if (newPO.getStartTime() == 0 && newPO.getEndTime() == 24) { TestCodeData po = oldCollect.stream().filter( p -> p.getStartTime() == 0 && p.getEndTime() == 24).findFirst().orElse(null); if (po == null) { po = oldCollect.stream().filter( p -> p.getStartTime() == 0 && p.getEndTime() == 12).findFirst().orElse(null); } if (po == null) { po = oldCollect.stream().filter( p -> p.getStartTime() == 12 && p.getEndTime() == 24).findFirst().orElse(null); } if (po != null) { newPO.setCity(po.getCity()); } } else if (newPO.getTimeUnit().equals(Integer.valueOf(1))) { TestCodeData po = oldCollect.stream().filter( e -> e.getTimeUnit().equals(Integer.valueOf(1))).findFirst().orElse(null); if (po != null) { newPO.setCity(po.getCity()); } } } }}復(fù)制代碼
現(xiàn)在利用 枚舉 來優(yōu)化 getCityNull 方法中的 for 循環(huán)部分代碼,我們可以看到這段代碼中有4段邏輯,總體形式如下:
if (newPO.getStartTime() == 0 && newPO.getEndTime() == 12) { //第一段邏輯} else if (newPO.getStartTime() == 12 && newPO.getEndTime() == 24) { //第二段邏輯} else if (newPO.getStartTime() == 0 && newPO.getEndTime() == 24) { //第三段邏輯} else if (newPO.getTimeUnit().equals(Integer.valueOf(1))) { //第四段邏輯} 復(fù)制代碼
按照這個思路利用枚舉進(jìn)行二次優(yōu)化,將其中的邏輯封裝到枚舉類中:
public enum TimeEnum { AM("am", "上午") { @Override public void setCity(TestCodeData data, List<TestCodeData> oldDataList) { TestCodeData po = oldDataList.stream().filter(p -> p.getStartTime() == 0 && (p.getEndTime() == 12 || p.getEndTime() == 24)).findFirst().orElse(null); if (null != po) { data.setCity(po.getCity()); } } }, PM("pm", "下午") { @Override public void setCity(TestCodeData data, List<TestCodeData> oldCollect) { TestCodeData po = oldCollect.stream().filter( p -> (p.getStartTime() == 12 || p.getStartTime() == 0) && p.getEndTime() == 24).findFirst().orElse(null); if (po != null) { data.setCity(po.getCity()); } } }, DAY("day", "全天") { @Override public void setCity(TestCodeData data, List<TestCodeData> oldCollect) { TestCodeData po = oldCollect.stream().filter( p -> p.getStartTime() == 0 && p.getEndTime() == 24).findFirst().orElse(null); if (po == null) { po = oldCollect.stream().filter( p -> p.getStartTime() == 0 && p.getEndTime() == 12).findFirst().orElse(null); } if (po == null) { po = oldCollect.stream().filter( p -> p.getStartTime() == 12 && p.getEndTime() == 24).findFirst().orElse(null); } if (po != null) { data.setCity(po.getCity()); } } }, HOUR("hour", "小時") { @Override public void setCity(TestCodeData data, List<TestCodeData> oldCollect) { TestCodeData po = oldCollect.stream().filter( e -> e.getTimeUnit().equals(Integer.valueOf(1))).findFirst().orElse(null); if (po != null) { data.setCity(po.getCity()); } } }; public abstract void setCity(TestCodeData data, List<TestCodeData> oldCollect); private String code; private String desc; TimeEnum(String code, String desc) { this.code = code; this.desc = desc; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; }}復(fù)制代碼
然后 getCityNull 方法中 for 循環(huán)部分邏輯如下:
for (TestCodeData data : newCollect) { if (data.getStartTime() == 0 && data.getEndTime() == 12) { TimeEnum.AM.setCity(data, oldCollect); } else if (data.getStartTime() == 12 && data.getEndTime() == 24) { TimeEnum.PM.setCity(data, oldCollect); } else if (data.getStartTime() == 0 && data.getEndTime() == 24) { TimeEnum.DAY.setCity(data, oldCollect); } else if (data.getTimeUnit().equals(Integer.valueOf(1))) { TimeEnum.HOUR.setCity(data, oldCollect); }}復(fù)制代碼
其實在這個業(yè)務(wù)場景中使用枚舉并不是特別合適,如果在遍歷對象時,我們就知道要執(zhí)行哪個枚舉類型,此時最合適,偽代碼如下:
for (TestCodeData data : newCollect) { String code = "am"; // 這里假設(shè) code 變量是從 data 中獲取的 TimeEnum.valueOf(code).setCity(data, oldCollect);}復(fù)制代碼
技巧四:函數(shù)式接口
業(yè)務(wù)場景描述:比如讓你做一個簡單的營銷拉新活動,這個活動投放到不同的渠道,不同渠道過來的用戶獎勵不一樣?,F(xiàn)假設(shè)在 頭條、微信 等渠道都投放了該活動。此時你的代碼可能會寫出如下形式:
@RestController@RequestMapping("/activity")public class ActivityController { @Resource private AwardService awardService; @PostMapping("/reward") public void reward(String userId, String source) { if ("toutiao".equals(source)) { awardService.toutiaoReward(userId); } else if ("wx".equals(source)) { awardService.wxReward(userId); } }}@Servicepublic class AwardService { private static final Logger log = LoggerFactory.getLogger(AwardService.class); public Boolean toutiaoReward(String userId) { log.info("頭條渠道用戶{}獎勵50元紅包!", userId); return Boolean.TRUE; } public Boolean wxReward(String userId) { log.info("微信渠道用戶{}獎勵100元紅包!", userId); return Boolean.TRUE; }}復(fù)制代碼
看完這段代碼,邏輯上是沒有什么問題的。但它有一個隱藏的缺陷,如果后期又增加很多渠道的時候,你該怎么辦?繼續(xù) else if 嗎?其實我們可以利用函數(shù)式接口優(yōu)化,當(dāng)然設(shè)計模式也可以優(yōu)化。這里我只是舉例使用一下函數(shù)式接口的使用方式。
@RestController@RequestMapping("/activity")public class ActivityController { @Resource private AwardService awardService; @PostMapping("/reward") public void reward(String userId, String source) { awardService.getRewardResult(userId, source); }}@Servicepublic class AwardService { private static final Logger log = LoggerFactory.getLogger(AwardService.class); private Map<String, BiFunction<String, String, Boolean>> sourceMap = new HashMap<>(); @PostConstruct private void dispatcher() { sourceMap.put("wx", (userId, source) -> this.wxReward(userId)); sourceMap.put("toutiao", (userId, source) -> this.toutiaoReward(userId)); } public Boolean getRewardResult(String userId, String source) { BiFunction<String, String, Boolean> result = sourceMap.get(source); if (null != result) { return result.apply(userId, source); } return Boolean.FALSE; } private Boolean toutiaoReward(String userId) { log.info("頭條渠道用戶{}獎勵50元紅包!", userId); return Boolean.TRUE; } private Boolean wxReward(String userId) { log.info("微信渠道用戶{}獎勵100元紅包!", userId); return Boolean.TRUE; }}復(fù)制代碼
針對一些復(fù)雜的業(yè)務(wù)場景,業(yè)務(wù)參數(shù)很多時,可以利用 @FunctionalInterface 自定義函數(shù)式接口來滿足你的業(yè)務(wù)需求,使用原理和本例并無差別。
技巧五:設(shè)計模式
??設(shè)計模式對于if-else的優(yōu)化,我個人覺得有些重,但是也是一種優(yōu)化方式。設(shè)計模式適合使用在大的業(yè)務(wù)流程和場景中使用,針對代碼塊中的if-else邏輯優(yōu)化不推薦使用。
常用的設(shè)計模式有:
- 策略模式
- 模板方法
- 工廠模式
- 單例模式
還是以上面的營銷拉新活動為例來說明如何使用。
使用技巧一:工廠模式 抽象類
- 定義抽象業(yè)務(wù)接口
public abstract class AwardAbstract { public abstract Boolean award(String userId);}復(fù)制代碼
- 定義具體業(yè)務(wù)實現(xiàn)類
// 頭條渠道發(fā)放獎勵業(yè)務(wù)public class TouTiaoAwardService extends AwardAbstract { @Override public Boolean award(String userId) { log.info("頭條渠道用戶{}獎勵50元紅包!", userId); return Boolean.TRUE; }}// 微信渠道發(fā)放獎勵業(yè)務(wù)public class WeChatAwardService extends AwardAbstract { @Override public Boolean award(String userId) { log.info("微信渠道用戶{}獎勵100元紅包!", userId); return Boolean.TRUE; }}復(fù)制代碼
- 利用工廠模式獲取實例對象
public class AwardFactory { public static AwardAbstract getAwardInstance(String source) { if ("toutiao".equals(source)) { return new TouTiaoAwardService(); } else if ("wx".equals(source)) { return new WeChatAwardService(); } return null; }}復(fù)制代碼
- 業(yè)務(wù)入口處根據(jù)不同渠道執(zhí)行不同的發(fā)放邏輯
@PostMapping("/reward2")public void reward2(String userId, String source) { AwardAbstract instance = AwardFactory.getAwardInstance(source); if (null != instance) { instance.award(userId); }}復(fù)制代碼
使用技巧二:策略模式 模板方法 工廠模式 單例模式
還是以營銷拉新為業(yè)務(wù)場景來說明,這個業(yè)務(wù)流程再增加一些復(fù)雜度,比如發(fā)放獎勵之前要進(jìn)行 身份驗證、風(fēng)控驗證 等一些列的校驗,此時你的業(yè)務(wù)流程該如何實現(xiàn)更清晰簡潔呢!
- 定義業(yè)務(wù)策略接口
/** 策略業(yè)務(wù)接口 */public interface AwardStrategy { /** * 獎勵發(fā)放接口 */ Map<String, Boolean> awardStrategy(String userId); /** * 獲取策略標(biāo)識,即不同渠道的來源標(biāo)識 */ String getSource();}復(fù)制代碼
- 定義獎勵發(fā)放模板流程
public abstract class BaseAwardTemplate { private static final Logger log = LoggerFactory.getLogger(BaseAwardTemplate.class); //獎勵發(fā)放模板方法 public Boolean awardTemplate(String userId) { this.authentication(userId); this.risk(userId); return this.awardRecord(userId); } //身份驗證 protected void authentication(String userId) { log.info("{} 執(zhí)行身份驗證!", userId); } //風(fēng)控 protected void risk(String userId) { log.info("{} 執(zhí)行風(fēng)控校驗!", userId); } //執(zhí)行獎勵發(fā)放 protected abstract Boolean awardRecord(String userId);}復(fù)制代碼
- 實現(xiàn)不同渠道的獎勵業(yè)務(wù)
@Slf4j@Servicepublic class ToutiaoAwardStrategyService extends BaseAwardTemplate implements AwardStrategy { /** * 獎勵發(fā)放接口 */ @Override public Boolean awardStrategy(String userId) { return super.awardTemplate(userId); } @Override public String getSource() { return "toutiao"; } /** * 具體的業(yè)務(wù)獎勵發(fā)放實現(xiàn) */ @Override protected Boolean awardRecord(String userId) { log.info("頭條渠道用戶{}獎勵50元紅包!", userId); return Boolean.TRUE; }}@Slf4j@Servicepublic class WeChatAwardStrategyService extends BaseAwardTemplate implements AwardStrategy { /** * 獎勵發(fā)放接口 */ @Override public Boolean awardStrategy(String userId) { return super.awardTemplate(userId); } @Override public String getSource() { return "wx"; } /*** * 具體的業(yè)務(wù)獎勵發(fā)放實現(xiàn) */ @Override protected Boolean awardRecord(String userId) { log.info("微信渠道用戶{}獎勵100元紅包!", userId); return Boolean.TRUE; }}復(fù)制代碼
- 定義工廠方法,對外統(tǒng)一暴露業(yè)務(wù)調(diào)用入口
@Componentpublic class AwardStrategyFactory implements ApplicationContextAware { private final static Map<String, AwardStrategy> MAP = new HashMap<>(); @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { Map<String, AwardStrategy> beanTypeMap = applicationContext.getBeansOfType(AwardStrategy.class); beanTypeMap.values().forEach(strategyObj -> MAP.put(strategyObj.getSource(), strategyObj)); } /** * 對外統(tǒng)一暴露的工廠方法 */ public Boolean getAwardResult(String userId, String source) { AwardStrategy strategy = MAP.get(source); if (Objects.isNull(strategy)) { throw new RuntimeException("渠道異常!"); } return strategy.awardStrategy(userId); } /** * 靜態(tài)內(nèi)部類創(chuàng)建單例工廠對象 */ private static class CreateFactorySingleton { private static AwardStrategyFactory factory = new AwardStrategyFactory(); } public static AwardStrategyFactory getInstance() { return CreateFactorySingleton.factory; }}復(fù)制代碼
- 業(yè)務(wù)入口方法
@RestController@RequestMapping("/activity")public class ActivityController { @PostMapping("/reward3") public void reward3(String userId, String source) { AwardStrategyFactory.getInstance().getAwardResult(userId, source); }}復(fù)制代碼
假如發(fā)起請求: POST http://localhost:8080/activity/reward3?userId=fei&source=wx
2022-02-20 12:23:27.716 INFO 20769 --- [nio-8080-exec-1] c.a.c.e.o.c.p.s.BaseAwardTemplate : fei 執(zhí)行身份驗證!2022-02-20 12:23:27.719 INFO 20769 --- [nio-8080-exec-1] c.a.c.e.o.c.p.s.BaseAwardTemplate : fei 執(zhí)行風(fēng)控校驗!2022-02-20 12:23:27.719 INFO 20769 --- [nio-8080-exec-1] a.c.e.o.c.p.s.WeChatAwardStrategyService : 微信渠道用戶fei獎勵100元紅包!復(fù)制代碼
其他技巧
- 使用三目運算符
- 相同業(yè)務(wù)邏輯提取復(fù)用