<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          消除代碼壞味道之減少嵌套if的一些方式

          共 9696字,需瀏覽 20分鐘

           ·

          2021-10-24 21:39

          【最近發(fā)生了一些事情,不過(guò)是,螞蟻緣槐夸大國(guó),蚍蜉撼樹(shù)談何易?!?br>


          在實(shí)際的代碼開(kāi)發(fā)過(guò)程中,經(jīng)常能夠見(jiàn)到一個(gè)類(lèi)或者方法中存在大量的if-else代碼塊,一個(gè)復(fù)雜的邏輯幾乎沒(méi)有方法的抽象和提取,只能根據(jù)if判斷條件去理解業(yè)務(wù)邏輯。這是典型的代碼壞味道。

          更有甚者else中還繼續(xù)嵌套if,甚至出現(xiàn)嵌套層級(jí)大于三層的情況,讓維護(hù)者很是頭痛,代碼如下所示。

          public void handle(String orderId, Double actualPrice, Double couponPrice) {
          if (StringUtils.isNotBlank(orderId)) {
          if (actualPrice != null) {
          if (couponPrice != null) {
          // 執(zhí)行業(yè)務(wù)邏輯
          doSomething();
          }
          }
          } else {
          // 其他邏輯
          }
          }

          這是一個(gè)簡(jiǎn)單的訂單處理邏輯,當(dāng)訂單號(hào)orderId、實(shí)際金額actualPrice以及券金額couponPrice均不為空時(shí)執(zhí)行業(yè)務(wù)邏輯,否則執(zhí)行其他邏輯。

          初學(xué)者常常喜歡將判斷屬性非空的邏輯通過(guò)嵌套if方式編寫(xiě),代碼閱讀體驗(yàn)比較差,如果邏輯復(fù)雜,則嵌套if代碼塊到處都是,無(wú)形中提高了理解成本。這里提供幾種優(yōu)化方式。

          使用一層判斷減少嵌套if

          首先介紹的方法是通過(guò)使用一層判斷減少嵌套if,具體的做法是對(duì)于嵌套if,通過(guò)條件運(yùn)算符對(duì)判斷條件進(jìn)行連接,如果判斷條件過(guò)長(zhǎng)則重構(gòu)提取為一個(gè)獨(dú)立的判斷方法,這樣代碼看起來(lái)就會(huì)比較精煉。

          public void handle2(String orderId, Double actualPrice, Double couponPrice) {
          if (isOrderParameterIllegal(orderId, actualPrice, couponPrice)) {
          // 執(zhí)行業(yè)務(wù)邏輯
          doSomething();
          } else {
          // 其他邏輯
          }
          }

          private boolean isOrderParameterIllegal(String orderId, Double actualPrice, Double couponPrice) {
          return StringUtils.isNotBlank(orderId)
          && actualPrice != null && couponPrice != null;
          }

          上述代碼中,將之前的嵌套if通過(guò)條件運(yùn)算符“&&”(AND)進(jìn)行連接,并將連接之后的復(fù)雜條件表達(dá)式單獨(dú)提取為一個(gè)方法,只有全部滿足才返回true,否則返回false。

          這樣處理之后,主流程的代碼就看起來(lái)比較清晰,閱讀體驗(yàn)比較好。這種通過(guò)合并判斷條件,并將判斷條件單獨(dú)提取的方式在開(kāi)發(fā)中是一種常見(jiàn)的消除嵌套if帶來(lái)的壞味道的方式。

          使用衛(wèi)語(yǔ)句減少嵌套if層級(jí)

          除了合并判斷條件外,還可以通過(guò)使用“衛(wèi)語(yǔ)句”的方式減少嵌套if層級(jí)。

          經(jīng)典軟件開(kāi)發(fā)著作《重構(gòu)》中是這樣定義“衛(wèi)語(yǔ)句”的:

          ?

          如果條件語(yǔ)句極其復(fù)雜,就應(yīng)該將條件語(yǔ)句拆解開(kāi),然后逐個(gè)檢查,并在條件為真時(shí)立刻從函數(shù)中返回,這樣的單獨(dú)檢查通常被稱之為“衛(wèi)語(yǔ)句”(guard clauses)。

          ?

          簡(jiǎn)單的說(shuō)就是對(duì)復(fù)雜條件進(jìn)行逐個(gè)檢查,從反向邏輯進(jìn)行考慮,一旦不滿足條件就直接返回,將不滿足條件的情況考慮完全之后,直接執(zhí)行剩下的正常邏輯即可。還是通過(guò)章節(jié)開(kāi)頭的案例進(jìn)行直觀展示,代碼如下:

          public void handle3(String orderId, Double actualPrice, Double couponPrice) {
          if (StringUtils.isBlank(orderId)) {
          // 一些善后邏輯
          otherThing();
          return;
          }
          if (actualPrice == null) {
          // 一些善后邏輯
          otherThing();
          return;
          }
          if (couponPrice == null) {
          // 一些善后邏輯
          otherThing();
          return;
          }
          // 執(zhí)行業(yè)務(wù)邏輯
          doSomething();
          }

          通過(guò)代碼案例可以看到,衛(wèi)語(yǔ)句是對(duì)各種異常條件進(jìn)行先行判斷,參數(shù)一旦滿足這些異常情況則直接結(jié)束業(yè)務(wù)邏輯,執(zhí)行一些善后操作后就返回了。

          當(dāng)所有考慮到的異常情況被校驗(yàn)之后,直接執(zhí)行正常的業(yè)務(wù)邏輯即可。這種代碼編寫(xiě)風(fēng)格直接消除了嵌套if,將代碼層級(jí)優(yōu)化為最多嵌套一層if,代碼閱讀難度大幅度降低,整體代碼風(fēng)格清新,條理,讓閱讀者心情暢快。這種風(fēng)格也是筆者比較推崇的,希望讀者朋友能夠吸收并加以運(yùn)用。

          使用策略模式消除復(fù)雜if判斷

          對(duì)于復(fù)雜場(chǎng)景的業(yè)務(wù)判斷,通過(guò)引入策略模式能夠完全消除if,在實(shí)際開(kāi)發(fā)場(chǎng)景中,這也是一種經(jīng)常使用到的編碼技巧。

          關(guān)于策略模式暫時(shí)不展開(kāi)講解,有經(jīng)驗(yàn)的同學(xué)自然用過(guò),沒(méi)有學(xué)過(guò)的同學(xué)可以去找資料學(xué)一下,比如說(shuō)《設(shè)計(jì)模式之禪》。

          假設(shè)有一個(gè)后端接口服務(wù),需要同時(shí)對(duì)App、微信小程序、支付寶小程序、PC網(wǎng)頁(yè)端提供服務(wù),此時(shí)有個(gè)需求要統(tǒng)計(jì)來(lái)自不同端的用戶行為,并針對(duì)不同來(lái)源的用戶進(jìn)行特定的操作,如圖所示。

          如果采用傳統(tǒng)的if-else或者switch-case方式進(jìn)行編碼,代碼看起來(lái)如下所示。

          public void handleByChannelType(ChannelType channelType) {
          if (channelType == ChannelType.PHONE_APP) {
          System.out.println("手機(jī)App渠道邏輯");
          } else if (channelType == ChannelType.PHONE_H5) {
          System.out.println("H5渠道邏輯");
          } else if (channelType == ChannelType.MICRO_APPLET_WECHAT) {
          System.out.println("微信小程序處理邏輯");
          } else if (channelType == ChannelType.MICRO_APPLET_ALIPAY) {
          System.out.println("支付寶小程序處理邏輯");
          } else {
          System.out.println("其他邏輯");
          }
          }

          public void handleByChannelType2(ChannelType channelType) {
          switch (channelType) {
          case PHONE_H5:
          System.out.println("手機(jī)App渠道邏輯");
          break;

          case PHONE_APP:
          System.out.println("H5渠道邏輯");
          break;

          case MICRO_APPLET_WECHAT:
          System.out.println("微信小程序處理邏輯");
          break;

          case MICRO_APPLET_ALIPAY:
          System.out.println("支付寶小程序處理邏輯");
          break;
          default:
          System.out.println("其他邏輯");
          }
          }

          代碼中的渠道類(lèi)型會(huì)一直增加,每個(gè)渠道內(nèi)部的代碼邏輯也會(huì)逐步修改與增加,隨著代碼邏輯被不斷修改,這部分代碼勢(shì)必會(huì)變得越來(lái)越復(fù)雜,最終難以維護(hù)。這是if-else以及switch-case在應(yīng)對(duì)復(fù)雜業(yè)務(wù)場(chǎng)景時(shí)天然的不足之處。

          那么此時(shí)如果使用策略模式進(jìn)行重構(gòu),則能夠很好的解決這個(gè)問(wèn)題。

          (1)首先定義一個(gè)接口提供一個(gè)getChannelType()方法,供不同的渠道實(shí)現(xiàn)類(lèi)進(jìn)行實(shí)現(xiàn),返回實(shí)際的渠道類(lèi)型;同時(shí)接口提供了doSomething()方法,表示抽象的業(yè)務(wù)邏輯,不同的渠道實(shí)現(xiàn)類(lèi)實(shí)現(xiàn)該方法,對(duì)外提供不同的業(yè)務(wù)邏輯實(shí)現(xiàn);

          public interface ChannelStrategy {
          /**具體的業(yè)務(wù)邏輯*/
          void doSomething();
          /**獲取渠道類(lèi)型*/
          ChannelType getChannelType();
          }

          (2)定義不同渠道的策略實(shí)現(xiàn)類(lèi),實(shí)現(xiàn)接口ChannelStrategy,支付寶小程序渠道代碼如下:

          public class MicroAppletAlipayStrategyImpl implements ChannelStrategy {
          @Override
          public void doSomething() {
          System.out.println("支付寶小程序處理邏輯");
          }
          @Override
          public ChannelType getChannelType() {
          return ChannelType.MICRO_APPLET_ALIPAY;
          }
          }

          微信小程序代碼如下:

          public class MicroAppletWechatStrategyImpl implements ChannelStrategy {
          @Override
          public void doSomething() {
          System.out.println("微信小程序處理邏輯");
          }
          @Override
          public ChannelType getChannelType() {
          return ChannelType.MICRO_APPLET_WECHAT;
          }
          }

          手機(jī)App渠道的策略實(shí)現(xiàn)如下:

          public class PhoneAppChannelStrategyImpl implements ChannelStrategy {
          @Override
          public void doSomething() {
          System.out.println("手機(jī)App渠道邏輯");
          }
          @Override
          public ChannelType getChannelType() {
          return ChannelType.PHONE_APP;
          }
          }

          手機(jī)H5頁(yè)面的渠道策略如下:

          public class PhoneH5ChannelStrategyImpl implements ChannelStrategy {
          @Override
          public void doSomething() {
          System.out.println("H5渠道邏輯");
          }
          @Override
          public ChannelType getChannelType() {
          return ChannelType.PHONE_H5;
          }
          }

          (3)接著定義策略上下文,該上下文中包含一個(gè)Map,上下文定義為單例,在實(shí)例初始化階段加載不同的渠道邏輯實(shí)現(xiàn)到Map中;

          public class ChannelStrategyContext {

          private static final Map CHANNEL_STRATEGY_MAP = new ConcurrentHashMap<>();

          private ChannelStrategyContext() {
          ChannelStrategy phoneH5 = new PhoneAppChannelStrategyImpl();
          ChannelStrategy phoneApp = new PhoneAppChannelStrategyImpl();
          ChannelStrategy appletAlipay = new MicroAppletAlipayStrategyImpl();
          ChannelStrategy appletWechat = new MicroAppletWechatStrategyImpl();

          CHANNEL_STRATEGY_MAP.put(phoneH5.getChannelType(), phoneH5);
          CHANNEL_STRATEGY_MAP.put(phoneApp.getChannelType(), phoneApp);
          CHANNEL_STRATEGY_MAP.put(appletAlipay.getChannelType(), appletAlipay);
          CHANNEL_STRATEGY_MAP.put(appletWechat.getChannelType(), appletWechat);
          }


          public static ChannelStrategyContext getInstance() {
          return Holder.singleton;
          }

          public void domeSomething(ChannelType channelType) {
          CHANNEL_STRATEGY_MAP.get(channelType).doSomething();
          }

          /**
          * 內(nèi)部類(lèi),為單例提供
          */
          private static class Holder {
          private static ChannelStrategyContext singleton = new ChannelStrategyContext();
          }
          }

          (4)接著在策略上下文中定義doSomething()方法,通過(guò)委托的方式根據(jù)方法參數(shù)中傳入的ChannelType從Map中篩選出對(duì)應(yīng)的渠道對(duì)象,并調(diào)用該渠道對(duì)象的doSomething()方法;

          編寫(xiě)一段代碼測(cè)試一下通過(guò)策略模式改寫(xiě)后的邏輯,假設(shè)上游傳遞的ChannelType類(lèi)型為微信小程序MICRO_APPLET_WECHAT,則業(yè)務(wù)邏輯代碼如下所示:

          public class Main {
          public static void main(String[] args) {
          ChannelType wechatApplet = ChannelType.MICRO_APPLET_WECHAT;
          ChannelStrategyContext.getInstance().domeSomething(wechatApplet);
          }
          }

          運(yùn)行測(cè)試代碼,觀察控制臺(tái)輸出如下:

          微信小程序處理邏輯
          Process finished with exit code 0

          可以看到,相比于if-else或者switch-case,主流程的代碼非常簡(jiǎn)潔,只需要一行代碼就能根據(jù)ChannelType執(zhí)行到對(duì)應(yīng)的邏輯,代碼邏輯更容易被人所理解。一旦需要增加新的渠道,只需要增加一個(gè)新的ChannelStrategy實(shí)現(xiàn)類(lèi),并添加到ChannelStrategyContext上下文中。這體現(xiàn)出了代碼編寫(xiě)中的開(kāi)發(fā)封閉原則,即對(duì)主業(yè)務(wù)流程的修改是關(guān)閉的,對(duì)渠道的新增是開(kāi)放的。

          相信有的讀者發(fā)現(xiàn),每次新增新的渠道實(shí)現(xiàn)都需要修改一下ChannelStrategyContext,還不夠友好,「有沒(méi)有一種方式能夠只增加渠道的實(shí)現(xiàn)就可以動(dòng)態(tài)的將新的渠道類(lèi)型注冊(cè)到ChannelStrategyContext中呢?」

          Spring集合注入方式動(dòng)態(tài)裝載策略容器

          其實(shí)是有的,可以通過(guò)基于Spring集合類(lèi)型注入的方式對(duì)接口所有實(shí)現(xiàn)類(lèi)批量注入從而避免手動(dòng)編寫(xiě)大量的put代碼,實(shí)現(xiàn)新增渠道實(shí)現(xiàn)類(lèi)不需要修改ChannelStrategyContext的目的。接下來(lái)對(duì)這種方式進(jìn)行講解,讀者可以根據(jù)自己的理解與喜好在實(shí)戰(zhàn)中加以應(yīng)用。

          首先介紹Spring集合類(lèi)型批量注入接口實(shí)現(xiàn)類(lèi)的方式,首先將ChannelStrategy的接口實(shí)現(xiàn)類(lèi)均標(biāo)注為Spring的Bean,如使用@Service、@Component注解,代碼如下(以支付寶渠道策略實(shí)現(xiàn)類(lèi)為例)。

          @Service
          public class MicroAppletAlipayStrategyImpl implements ChannelStrategy {
          @Override
          public void doSomething() {
          System.out.println("支付寶小程序處理邏輯");
          }
          @Override
          public ChannelType getChannelType() {
          return ChannelType.MICRO_APPLET_ALIPAY;
          }
          }

          接著編寫(xiě)Spring配置類(lèi),通過(guò)@ComponentScan注解配置掃描包,開(kāi)啟注解支持。配置類(lèi)代碼如下:

          @Configuration
          @ComponentScan(basePackages = {"com.snowalker.from.distributed.to.cloudnative.section11_4.channeldemo.spring_collection_inject"})
          public class BeanConfig {
          }

          ?接著編寫(xiě)ChannelStrategySpringContext策略上下文,通過(guò)@Autowired注入List< ChannelStrategy>集合,完整的ChannelStrategySpringContext代碼如下:

          @Service
          public class ChannelStrategySpringContext {

          private static final Map CHANNEL_STRATEGY_MAP = new ConcurrentHashMap<>();

          @Autowired
          List channelStrategies;

          @PostConstruct
          public void init() {
          channelStrategies.stream().forEach(channelStrategy -> {
          CHANNEL_STRATEGY_MAP.put(channelStrategy.getChannelType(), channelStrategy);
          });
          }

          public void domeSomething(ChannelType channelType) {
          CHANNEL_STRATEGY_MAP.get(channelType).doSomething();
          }
          }

          對(duì)比上面未使用Spring框架的原生Java實(shí)現(xiàn)的ChannelStrategyContext,此處的ChannelStrategySpringContext代碼量更少,邏輯更加簡(jiǎn)潔。

          通過(guò)直接注入List,Spring框架會(huì)在Bean的加載階段,將ChannelStrategy接口實(shí)現(xiàn)類(lèi)裝載到集合List中,這部分工作在Spring的DefaultListableBeanFactory.resolveDependency()中完成,感興趣的讀者可以自行查看。

          通過(guò)@PostContruct注解標(biāo)注的init()方法,將ChannelStrategy接口的所有實(shí)例解析出來(lái)并加載到Map中,方便在方法中根據(jù)具體的ChannelType獲取對(duì)應(yīng)的ChannelStrategy實(shí)現(xiàn)。

          其他方法和原生Java實(shí)現(xiàn)相同,但是省略了內(nèi)部類(lèi)以及獲取單例方法,原因在于Spring中Bean默認(rèn)為單例,因此不需要再顯式書(shū)寫(xiě)單例相關(guān)的代碼。

          為方便讀者理解,將這部分邏輯用一張流程圖展示如圖所示。

          最后編寫(xiě)測(cè)試代碼進(jìn)行調(diào)用,依舊指定渠道類(lèi)型ChannelType為微信小程序,測(cè)試代碼如下:

          public class Client {
          public static void main(String[] args) {
          ApplicationContext applicationContext =
          new AnnotationConfigApplicationContext(BeanConfig.class);
          ChannelStrategySpringContext channelStrategySpringContext =
          applicationContext.getBean("channelStrategySpringContext", ChannelStrategySpringContext.class);
          channelStrategySpringContext
          .domeSomething(ChannelType.MICRO_APPLET_WECHAT);
          }
          }

          代碼邏輯為:先定義AnnotationConfigApplicationContext上下文,加載BeanConfig配置類(lèi),開(kāi)啟注解支持。然后從上下文中根據(jù)bean名稱獲取到ChannelStrategySpringContext的實(shí)例,調(diào)用ChannelStrategySpringContext的domeSomething(ChannelType channelType)方法,指定ChannelType為微信小程序ChannelType.MICRO_APPLET_WECHAT。代碼運(yùn)行結(jié)果如下:

          微信小程序處理邏輯
          Process finished with exit code 0

          到此就對(duì)如何基于策略模式消除代碼中的if邏輯進(jìn)行了充分的講解,希望能夠?qū)ψx者提升代碼質(zhì)量,消除過(guò)多if帶來(lái)的壞味道有所幫助。

          后記:本文是新書(shū)的第11章節(jié)的節(jié)選,主要是講解了開(kāi)發(fā)中常用的消除嵌套if的一些策略,更多內(nèi)容正在持續(xù)輸出中,敬請(qǐng)期待。

          瀏覽 192
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  操逼视频网站大全 | 亚洲黄色在线网站 | se色综合 | 人人操人人插人人摸人人爽人人 | yy4080午夜一级 |