自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

真香!我終于干掉了該死的if-else

開發(fā) 后端 開發(fā)工具
if else 是所有高級編程語言都有的必備功能。但現(xiàn)實(shí)中的代碼往往存在著過多的 if else。

 if else 是所有高級編程語言都有的必備功能。但現(xiàn)實(shí)中的代碼往往存在著過多的 if else。

[[321538]]

圖片來自 Pexels

雖然 if else 是必須的,但濫用 if else 會對代碼的可讀性、可維護(hù)性造成很大傷害,進(jìn)而危害到整個軟件系統(tǒng)。

現(xiàn)在軟件開發(fā)領(lǐng)域出現(xiàn)了很多新技術(shù)、新概念,但 if...else 這種基本的程序形式并沒有發(fā)生太大變化。

使用好 if else 不僅對于現(xiàn)在,而且對于將來,都是十分有意義的。今天我們就來看看如何“干掉”代碼中的 if else,還代碼以清爽。

問題一:if else 過多

問題表現(xiàn)

if else 過多的代碼可以抽象為下面這段代碼。其中只列出 5 個邏輯分支,但實(shí)際工作中,能見到一個方法包含 10 個、20 個甚至更多的邏輯分支的情況。

另外,if else 過多通常會伴隨著另兩個問題:邏輯表達(dá)式復(fù)雜和 if else 嵌套過深。

對于后兩個問題,本文將在下面兩節(jié)介紹。本節(jié)先來討論 if else 過多的情況。

  1. if (condition1) { 
  2. else if (condition2) { 
  3. else if (condition3) { 
  4. else if (condition4) { 
  5. else { 

通常,if else 過多的方法,通??勺x性和可擴(kuò)展性都不好。

從軟件設(shè)計(jì)角度講,代碼中存在過多的 if else 往往意味著這段代碼違反了違反單一職責(zé)原則和開閉原則。

因?yàn)樵趯?shí)際的項(xiàng)目中,需求往往是不斷變化的,新需求也層出不窮。所以,軟件系統(tǒng)的擴(kuò)展性是非常重要的。

而解決 if else 過多問題的最大意義,往往就在于提高代碼的可擴(kuò)展性。

如何解決

接下來我們來看如何解決 ifelse 過多的問題,下面我列出了一些解決方法:

  • 表驅(qū)動
  • 職責(zé)鏈模式
  • 注解驅(qū)動
  • 事件驅(qū)動
  • 有限狀態(tài)機(jī)
  • Optional
  • Assert
  • 多態(tài)

方法一:表驅(qū)動

對于邏輯表達(dá)模式固定的 if else 代碼,可以通過某種映射關(guān)系,將邏輯表達(dá)式用表格的方式表示;再使用表格查找的方式,找到某個輸入所對應(yīng)的處理函數(shù),使用這個處理函數(shù)進(jìn)行運(yùn)算。

適用場景:邏輯表達(dá)模式固定的 if else。

實(shí)現(xiàn)與示例:

  1. if (param.equals(value1)) { 
  2.     doAction1(someParams); 
  3. else if (param.equals(value2)) { 
  4.     doAction2(someParams); 
  5. else if (param.equals(value3)) { 
  6.     doAction3(someParams); 
  7. // ... 

可重構(gòu)為:

  1. Map<?, Function<?> action> actionMappings = new HashMap<>(); // 這里泛型 ? 是為方便演示,實(shí)際可替換為你需要的類型 
  2.  
  3. // When init 
  4. actionMappings.put(value1, (someParams) -> { doAction1(someParams)}); 
  5. actionMappings.put(value2, (someParams) -> { doAction2(someParams)}); 
  6. actionMappings.put(value3, (someParams) -> { doAction3(someParams)}); 
  7.  
  8. // 省略 null 判斷 
  9. actionMappings.get(param).apply(someParams); 

上面的示例使用了 Java 8 的 Lambda 和 Functional Interface,這里不做講解。

表的映射關(guān)系,可以采用集中的方式,也可以采用分散的方式,即每個處理類自行注冊。也可以通過配置文件的方式表達(dá)。總之,形式有很多。

還有一些問題,其中的條件表達(dá)式并不像上例中的那樣簡單,但稍加變換,同樣可以應(yīng)用表驅(qū)動。

下面借用《編程珠璣》中的一個稅金計(jì)算的例子:

  1. if income <= 2200 
  2.   tax = 0 
  3. else if income <= 2700 
  4.   tax = 0.14 * (income - 2200) 
  5. else if income <= 3200 
  6.   tax = 70 + 0.15 * (income - 2700) 
  7. else if income <= 3700 
  8.   tax = 145 + 0.16 * (income - 3200) 
  9. ...... 
  10. else 
  11.   tax = 53090 + 0.7 * (income - 102200) 

對于上面的代碼,其實(shí)只需將稅金的計(jì)算公式提取出來,將每一檔的標(biāo)準(zhǔn)提取到一個表格,在加上一個循環(huán)即可。具體重構(gòu)之后的代碼不給出,大家自己思考。

方法二:職責(zé)鏈模式

當(dāng) if else 中的條件表達(dá)式靈活多變,無法將條件中的數(shù)據(jù)抽象為表格并用統(tǒng)一的方式進(jìn)行判斷時,這時應(yīng)將對條件的判斷權(quán)交給每個功能組件。并用鏈的形式將這些組件串聯(lián)起來,形成完整的功能。

適用場景:條件表達(dá)式靈活多變,沒有統(tǒng)一的形式。

實(shí)現(xiàn)與示例:職責(zé)鏈的模式在開源框架的 Filter、Interceptor 功能的實(shí)現(xiàn)中可以見到很多。下面看一下通用的使用模式。

重構(gòu)前:

  1. public void handle(request) { 
  2.     if (handlerA.canHandle(request)) { 
  3.         handlerA.handleRequest(request); 
  4.     } else if (handlerB.canHandle(request)) { 
  5.         handlerB.handleRequest(request); 
  6.     } else if (handlerC.canHandle(request)) { 
  7.         handlerC.handleRequest(request); 
  8.     } 

重構(gòu)后:

  1. public void handle(request) { 
  2.   handlerA.handleRequest(request); 
  3.  
  4. public abstract class Handler { 
  5.   protected Handler next
  6.   public abstract void handleRequest(Request request); 
  7.   public void setNext(Handler next) { this.next = next; } 
  8.  
  9. public class HandlerA extends Handler { 
  10.   public void handleRequest(Request request) { 
  11.     if (canHandle(request)) doHandle(request); 
  12.     else if (next != nullnext.handleRequest(request); 
  13.   } 

當(dāng)然,示例中的重構(gòu)前的代碼為了表達(dá)清楚,做了一些類和方法的抽取重構(gòu)。現(xiàn)實(shí)中,更多的是平鋪式的代碼實(shí)現(xiàn)。

注:職責(zé)鏈的控制模式,職責(zé)鏈模式在具體實(shí)現(xiàn)過程中,會有一些不同的形式。從鏈的調(diào)用控制角度看,可分為外部控制和內(nèi)部控制兩種。

外部控制不靈活,但是減少了實(shí)現(xiàn)難度。職責(zé)鏈上某一環(huán)上的具體實(shí)現(xiàn)不用考慮對下一環(huán)的調(diào)用,因?yàn)橥獠拷y(tǒng)一控制了。

但是一般的外部控制也不能實(shí)現(xiàn)嵌套調(diào)用。如果有嵌套調(diào)用,并且希望由外部控制職責(zé)鏈的調(diào)用,實(shí)現(xiàn)起來會稍微復(fù)雜。具體可以參考 Spring Web Interceptor 機(jī)制的實(shí)現(xiàn)方法。

內(nèi)部控制就比較靈活,可以由具體的實(shí)現(xiàn)來決定是否需要調(diào)用鏈上的下一環(huán)。但如果調(diào)用控制模式是固定的,那這樣的實(shí)現(xiàn)對于使用者來說是不便的。

設(shè)計(jì)模式在具體使用中會有很多變種,大家需要靈活掌握。

方法三:注解驅(qū)動

通過 Java 注解(或其他語言的類似機(jī)制)定義執(zhí)行某個方法的條件。在程序執(zhí)行時,通過對比入?yún)⑴c注解中定義的條件是否匹配,再決定是否調(diào)用此方法。具體實(shí)現(xiàn)時,可以采用表驅(qū)動或職責(zé)鏈的方式實(shí)現(xiàn)。

適用場景:適合條件分支很多多,對程序擴(kuò)展性和易用性均有較高要求的場景。通常是某個系統(tǒng)中經(jīng)常遇到新需求的核心功能。

實(shí)現(xiàn)與示例:很多框架中都能看到這種模式的使用,比如常見的 Spring MVC。

因?yàn)檫@些框架很常用,Demo 隨處可見,所以這里不再上具體的演示代碼了。

這個模式的重點(diǎn)在于實(shí)現(xiàn)。現(xiàn)有的框架都是用于實(shí)現(xiàn)某一特定領(lǐng)域的功能,例如 MVC。

故業(yè)務(wù)系統(tǒng)如采用此模式需自行實(shí)現(xiàn)相關(guān)核心功能。主要會涉及反射、職責(zé)鏈等技術(shù)。具體的實(shí)現(xiàn)這里就不做演示了。

方法四:事件驅(qū)動

通過關(guān)聯(lián)不同的事件類型和對應(yīng)的處理機(jī)制,來實(shí)現(xiàn)復(fù)雜的邏輯,同時達(dá)到解耦的目的。

適用場景:從理論角度講,事件驅(qū)動可以看做是表驅(qū)動的一種,但從實(shí)踐角度講,事件驅(qū)動和前面提到的表驅(qū)動有多處不同。

具體來說:

  • 表驅(qū)動通常是一對一的關(guān)系;事件驅(qū)動通常是一對多。
  • 表驅(qū)動中,觸發(fā)和執(zhí)行通常是強(qiáng)依賴;事件驅(qū)動中,觸發(fā)和執(zhí)行是弱依賴。

正是上述兩者不同,導(dǎo)致了兩者適用場景的不同。具體來說,事件驅(qū)動可用于如訂單支付完成觸發(fā)庫存、物流、積分等功能。

實(shí)現(xiàn)與示例:實(shí)現(xiàn)方式上,單機(jī)的實(shí)踐驅(qū)動可以使用 Guava、Spring 等框架實(shí)現(xiàn)。分布式的則一般通過各種消息隊(duì)列方式實(shí)現(xiàn)。

但是因?yàn)檫@里主要討論的是消除 if else,所以主要是面向單機(jī)問題域。因?yàn)樯婕熬唧w技術(shù),所以此模式代碼不做演示。

方法五:有限狀態(tài)機(jī)

有限狀態(tài)機(jī)通常被稱為狀態(tài)機(jī)(無限狀態(tài)機(jī)這個概念可以忽略)。先引用維基百科上的定義:

有限狀態(tài)機(jī)(英語:finite-state machine,縮寫:FSM),簡稱狀態(tài)機(jī),是表示有限個狀態(tài)以及在這些狀態(tài)之間的轉(zhuǎn)移和動作等行為的數(shù)學(xué)模型。

其實(shí),狀態(tài)機(jī)也可以看做是表驅(qū)動的一種,其實(shí)就是當(dāng)前狀態(tài)和事件兩者組合與處理函數(shù)的一種對應(yīng)關(guān)系。當(dāng)然,處理成功之后還會有一個狀態(tài)轉(zhuǎn)移處理。

適用場景:雖然現(xiàn)在互聯(lián)網(wǎng)后端服務(wù)都在強(qiáng)調(diào)無狀態(tài),但這并不意味著不能使用狀態(tài)機(jī)這種設(shè)計(jì)。

其實(shí),在很多場景中,如協(xié)議棧、訂單處理等功能中,狀態(tài)機(jī)有這其天然的優(yōu)勢。因?yàn)檫@些場景中天然存在著狀態(tài)和狀態(tài)的流轉(zhuǎn)。

實(shí)現(xiàn)與示例:實(shí)現(xiàn)狀態(tài)機(jī)設(shè)計(jì)首先需要有相應(yīng)的框架,這個框架需要實(shí)現(xiàn)至少一種狀態(tài)機(jī)定義功能,以及對于的調(diào)用路由功能。

狀態(tài)機(jī)定義可以使用 DSL 或者注解的方式。原理不復(fù)雜,掌握了注解、反射等功能的同學(xué)應(yīng)該可以很容易實(shí)現(xiàn)。

參考技術(shù):

①Apache Mina State Machine

Apache Mina 框架,雖然在 IO 框架領(lǐng)域不及 Netty,但它卻提供了一個狀態(tài)機(jī)的功能。

有自己實(shí)現(xiàn)狀態(tài)機(jī)功能的同學(xué)可以參考其源碼:

  1. https://mina.apache.org/mina-project/userguide/ch14-state-machine/ch14-state-machine.html 

②Spring State Machine

Spring 子項(xiàng)目眾多,其中有個不顯山不露水的狀態(tài)機(jī)框架,可以通過 DSL 和注解兩種方式定義。

  1. https://projects.spring.io/spring-statemachine/ 

上述框架只是起到一個參考的作用,如果涉及到具體項(xiàng)目,需要根據(jù)業(yè)務(wù)特點(diǎn)自行實(shí)現(xiàn)狀態(tài)機(jī)的核心功能。

方法六:Optional

Java 代碼中的一部分 if else 是由非空檢查導(dǎo)致的。因此,降低這部分帶來的 if else 也就能降低整體的 if else 的個數(shù)。

Java 從 8 開始引入了 Optional 類,用于表示可能為空的對象。這個類提供了很多方法,用于相關(guān)的操作,可以用于消除 if else。開源框架 Guava 和 Scala 語言也提供了類似的功能。

使用場景:有較多用于非空判斷的 if else。

實(shí)現(xiàn)與示例如下,傳統(tǒng)寫法:

  1. String str = "Hello World!"
  2. if (str != null) { 
  3.     System.out.println(str); 
  4. else { 
  5.     System.out.println("Null"); 

使用 Optional 之后:

  1. Optional<String> strOptional = Optional.of("Hello World!"); 
  2. strOptional.ifPresentOrElse(System.out::println, () -> System.out.println("Null")); 

Optional 還有很多方法,這里不一一介紹了。但請注意,不要使用 get() 和 isPresent() 方法,否則和傳統(tǒng)的 if else 無異。

擴(kuò)展:Kotlin Null Safety

Kotlin 帶有一個被稱為 Null Safety 的特性:

  1. bob?.department?.head?.name 

對于一個鏈?zhǔn)秸{(diào)用,在 Kotlin 語言中可以通過?避免空指針異常。如果某一環(huán)為 null,那整個鏈?zhǔn)奖磉_(dá)式的值便為 null。

方法七:Assert 模式

上一個方法適用于解決非空檢查場景所導(dǎo)致的 if else,類似的場景還有各種參數(shù)驗(yàn)證,比如還有字符串不為空等等。

很多框架類庫,例如 Spring、Apache Commons 都提供了工具里,用于實(shí)現(xiàn)這種通用的功能。

這樣大家就不必自行編寫 if else 了:

  • Apache Commons Lang 中的 Validate 類:
  1. https://commons.apache.org/proper/commons-lang/javadocs/api-3.1/org/apache/commons/lang3/Validate.html 
  • Spring 的 Assert 類:
  1. https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/Assert.html 

使用場景:通常用于各種參數(shù)校驗(yàn)。

擴(kuò)展:Bean Validation

類似上一個方法,介紹 Assert 模式順便介紹一個有類似作用的技術(shù)—Bean Validation。

Bean Validation 是 Java EE 規(guī)范中的一個。Bean Validation 通過在 Java Bean 上用注解的方式定義驗(yàn)證標(biāo)準(zhǔn),然后通過框架統(tǒng)一進(jìn)行驗(yàn)證。也可以起到了減少 if else 的作用。

方法八:多態(tài)

使用面向?qū)ο蟮亩鄳B(tài),也可以起到消除 if else 的作用。

在代碼重構(gòu)這本書中,對此也有介紹:

  1. https://refactoring.com/catalog/replaceConditionalWithPolymorphism.html 

使用場景:鏈接中給出的示例比較簡單,無法體現(xiàn)適合使用多態(tài)消除 if else 的具體場景。

一般來說,當(dāng)一個類中的多個方法都有類似于示例中的 if else 判斷,且條件相同,那就可以考慮使用多態(tài)的方式消除 if else。

同時,使用多態(tài)也不是徹底消除 if else。而是將 if else 合并轉(zhuǎn)移到了對象的創(chuàng)建階段。在創(chuàng)建階段的 if..,我們可以使用前面介紹的方法處理。

小結(jié):上面這節(jié)介紹了 if else 過多所帶來的問題,以及相應(yīng)的解決方法。除了本節(jié)介紹的方法,還有一些其他的方法。

比如,在《重構(gòu)與模式》一書中就介紹了“用 Strategy 替換條件邏輯”、“用 State 替換狀態(tài)改變條件語句”和“用 Command 替換條件調(diào)度程序”這三個方法。

其中的“Command 模式”,其思想同本文的“表驅(qū)動”方法大體一致。另兩種方法,因?yàn)樵凇吨貥?gòu)與模式》一書中已做詳細(xì)講解,這里就不再重復(fù)。

何時使用何種方法,取決于面對的問題的類型。上面介紹的一些適用場景,只是一些建議,更多的需要開發(fā)人員自己的思考。

問題二:if else 嵌套過深

問題表現(xiàn)

if else 多通常并不是最嚴(yán)重的的問題。有的代碼 if else 不僅個數(shù)多,而且 if else 之間嵌套的很深,也很復(fù)雜,導(dǎo)致代碼可讀性很差,自然也就難以維護(hù)。

  1. if (condition1) { 
  2.     action1(); 
  3.     if (condition2) { 
  4.         action2(); 
  5.         if (condition3) { 
  6.             action3(); 
  7.             if (condition4) { 
  8.                 action4(); 
  9.             } 
  10.         } 
  11.     } 

if else 嵌套過深會嚴(yán)重地影響代碼的可讀性。當(dāng)然,也會有上一節(jié)提到的兩個問題。

如何解決

上一節(jié)介紹的方法也可用用來解決本節(jié)的問題,所以對于上面的方法,此節(jié)不做重復(fù)介紹。

這一節(jié)重點(diǎn)一些方法,這些方法并不會降低 if else 的個數(shù),但是會提高代碼的可讀性:

  • 抽取方法
  • 衛(wèi)語句

方法一:抽取方法

抽取方法是代碼重構(gòu)的一種手段。定義很容易理解,就是將一段代碼抽取出來,放入另一個單獨(dú)定義的方法。

適用場景:if else 嵌套嚴(yán)重的代碼,通??勺x性很差。故在進(jìn)行大型重構(gòu)前,需先進(jìn)行小幅調(diào)整,提高其代碼可讀性。抽取方法便是最常用的一種調(diào)整手段。

實(shí)現(xiàn)與示例如下,重構(gòu)前:

  1. public void add(Object element) { 
  2.   if (!readOnly) { 
  3.     int newSize = size + 1; 
  4.     if (newSize > elements.length) { 
  5.       Object[] newElements = new Object[elements.length + 10]; 
  6.       for (int i = 0; i < size; i++) { 
  7.         newElements[i] = elements[i]; 
  8.       } 
  9.  
  10.       elements = newElements 
  11.     } 
  12.     elements[size++] = element; 
  13.   } 

重構(gòu)后:

  1. public void add(Object element) { 
  2.   if (readOnly) { 
  3.     return
  4.   } 
  5.  
  6.   if (overCapacity()) { 
  7.     grow(); 
  8.   } 
  9.  
  10.   addElement(element); 

方法二:衛(wèi)語句

在代碼重構(gòu)中,有一個方法被稱為“使用衛(wèi)語句替代嵌套條件語句”。

直接看代碼:

  1. double getPayAmount() { 
  2.     double result; 
  3.     if (_isDead) result = deadAmount(); 
  4.     else { 
  5.         if (_isSeparated) result = separatedAmount(); 
  6.         else { 
  7.             if (_isRetired) result = retiredAmount(); 
  8.             else result = normalPayAmount(); 
  9.         }; 
  10.     } 
  11.     return result; 

重構(gòu)之后:

  1. double getPayAmount() { 
  2.     if (_isDead) return deadAmount(); 
  3.     if (_isSeparated) return separatedAmount(); 
  4.     if (_isRetired) return retiredAmount(); 
  5.     return normalPayAmount(); 

使用場景:當(dāng)看到一個方法中,某一層代碼塊都被一個 if else 完整控制時,通??梢圆捎眯l(wèi)語句。

問題三:if else 表達(dá)式過于復(fù)雜

問題表現(xiàn)

if else 所導(dǎo)致的第三個問題來自過于復(fù)雜的條件表達(dá)式。下面給個簡單的例子。

當(dāng) condition 1、2、3、4 分別為 true、false,請大家排列組合一下下面表達(dá)式的結(jié)果:

  1. if ((condition1 && condition2 ) || ((condition2 || condition3) && condition4)) { 
  2.  

我想沒人愿意干上面的事情。關(guān)鍵是,這一大坨表達(dá)式的含義是什么?關(guān)鍵便在于,當(dāng)不知道表達(dá)式的含義時,沒人愿意推斷它的結(jié)果。

所以,表達(dá)式復(fù)雜,并不一定是錯。但是表達(dá)式難以讓人理解就不好了。

如何解決

對于 if else 表達(dá)式復(fù)雜的問題,主要用代碼重構(gòu)中的抽取方法、移動方法等手段解決。因?yàn)檫@些方法在《代碼重構(gòu)》一書中都有介紹,所以這里不再重復(fù)。

總結(jié)

本文一共介紹了 10 種(算上擴(kuò)展有 12 種)用于消除、簡化 if else 的方法。

還有一些方法,如通過策略模式、狀態(tài)模式等手段消除 if else 在《重構(gòu)與模式》一書中也有介紹。

正如前言所說,if else 是代碼中的重要組成部分,但是過度、不必要地使用 if else,會對代碼的可讀性、可擴(kuò)展性造成負(fù)面影響,進(jìn)而影響到整個軟件系統(tǒng)。

“干掉”if else 的能力高低反映的是程序員對軟件重構(gòu)、設(shè)計(jì)模式、面向?qū)ο笤O(shè)計(jì)、架構(gòu)模式、數(shù)據(jù)結(jié)構(gòu)等多方面技術(shù)的綜合運(yùn)用能力,反映的是程序員的內(nèi)功。

要合理使用 if else,不能沒有設(shè)計(jì),也不能過度設(shè)計(jì)。這些對技術(shù)的綜合、合理地運(yùn)用都需要程序員在工作中不斷的摸索總結(jié)。

 

責(zé)任編輯:武曉燕 來源: 博客園
相關(guān)推薦

2021-01-29 07:45:27

if-else代碼數(shù)據(jù)

2020-10-22 09:20:22

SQLNoSQL 數(shù)據(jù)庫

2021-04-20 08:02:08

業(yè)務(wù)數(shù)據(jù)用戶

2019-11-26 10:07:10

業(yè)務(wù)開發(fā)邏輯

2023-06-02 07:30:24

If-else結(jié)構(gòu)流程控制

2019-10-22 09:11:50

策略業(yè)務(wù)代碼

2014-12-01 11:20:28

Win8.1微軟

2013-03-06 10:28:57

ifJava

2020-04-02 14:07:30

微信QQ轉(zhuǎn)賬

2022-01-13 10:45:59

if-else代碼Java

2021-04-13 06:39:13

代碼重構(gòu)code

2021-03-10 07:20:43

if-else靜態(tài)代碼

2020-11-09 14:03:51

Spring BootMaven遷移

2021-11-04 08:53:00

if-else代碼Java

2020-07-21 08:06:05

日志

2022-07-11 08:16:55

策略模式if-else

2020-12-15 09:31:58

CTOif-else代碼

2020-05-13 14:15:25

if-else代碼前端

2025-04-24 08:40:00

JavaScript代碼return語句

2018-03-23 05:25:18

5GWiFi網(wǎng)絡(luò)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號