天天看點

爛慫if-else代碼優化方案

作者:京東雲開發者

0.問題概述

代碼可讀性是衡量代碼品質的重要标準,可讀性也是可維護性、可擴充性的保證,因為代碼是連接配接程式員和機器的中間橋梁,要對雙邊友好。Quora 上有一個文章: “What are some of the most basic things every programmer should know?”

其中:

  • Code that’s hard to understand is hard to maintain.
  • Code that’s hard to maintain is next to useless.

也強調了"easy understand"代碼的重要性。

寫這篇文章的契機是在研讀Apache ShenYu項目時,看到了很大一坨的if else語句,如下:

爛慫if-else代碼優化方案

這裡并非評論這段代碼寫法有問題,因為我還并沒有深入到項目細節之中,可能這已經是多輪優化的結果嘞。

但是這個多層if else的形式引發了我的思考,因為我也曾在項目代碼中引入過如此繁重的if else結構,并在Code Review中被指出了問題。從那以後,我對if else的最大容忍層數就是三層。

我把大量if else的場景按照深度和廣度兩個次元劃分為兩種情況:

  • 嵌套層級過深
  • 平鋪範圍太廣

下面就讨論一下,當代碼中存在大量這樣結構的代碼的時候,該如何優化?

1.解決方案

1.1 盡早傳回

又稱衛語句,即Guard Statement

WikiPedia:

In computer programming, a guard is a boolean expression that must evaluate to true if the program execution is to continue in the branch in question.

Regardless of which programming language is used, a guard clause, guard code, or guard statement, is a check of integrity preconditions used to avoid errors during execution. A typical example is checking that a reference about to be processed is not null, which avoids null-pointer failures. Other uses include using a boolean field for idempotence (so subsequent calls are nops), as in the dispose pattern. The guard provides an early exit from a subroutine, and is a commonly used deviation from structured programming, removing one level of nesting and resulting in flatter code:[1] replacing if guard { ... } with if not guard: return; ....

實際應用:

if (CollectionUtils.isNotEmpty(list)) {
	// do something
} else {   
	return xxx;
}
           

使用盡早傳回優化:

if (CollectionUtils.isEmpty(list)) {
	return xxx;
}

// do something
           

可以看到,優化後的代碼不僅節省了一個else語句,也能讓後續的"do something"節省一層if else包裹,代碼看起來更幹淨一些

結合這個例子再說一下我對衛語句的了解:

可以将“衛”了解為“門衛”,門衛的作用是檢查過濾,隻有符合條件的語句,才可以繼續執行,否則直接勸返(return)。吐槽一下這種中文直譯有些晦澀,未免有點“德先生賽先生”的意思了。。。

1.2 使用switch或三元運算符

可以利用文法知識,對if else進行簡化,

例如,當if else滿足一定條件時:

if (condition1) {
    doSomeThing1();
} else if (condition2) {
    doSomeThing2();
} else if (condition3) {
    doSomeThing3(); 
} else if (condition4) {
    doSomeThing4();
} else {
    doSomeThing5(); 
}...
           

可以使用switch case文法進行替換

或,

例如使用三元運算符進行指派操作:

Integer num = obejct == null ? 1 : object.value();

1.3 政策模式

1.3.1 概念

政策模式是一種行為設計模式,即一個對象有一個确定的行為,在不同場景下,這些行為有不同的算法實作。

例如從内蒙通過公共交通去北京是一個确定的行為,在天上這種場景可以選擇飛機,地上的場景可以選擇火車~

政策模式一般包含三個要素:

  • 抽象政策(Abstract strategy):定義所謂的“确定的行為”,一般由接口或抽象類實作
  • 具體實作(Concrete strategy):封裝對應場景下的具體算法實作。
  • 上下文(Context):負責具體實作政策的管理并供對象使用。

1.3.2 使用場景

  • 一個接口或抽象類的各個子類都是為了解決相同的問題,區分這些子類的隻有方法實作的不同。
  • 代碼中使用大量if else或大面積switch case來選擇具體的子實作類

1.3.3 實際應用

例如:

if ("man".equals(strategy)) {   
	// Perform related operations 
} else if ("woman".equals(strategy)) {   
	// Perform operations related to women
} else if ("other".equals(strategy)) {   
	// Perform other operations
}
           

上面一段代碼,每一個if分支完成的都是相同的操作,隻是在不同的性别場景下,操作方法的實作不同,那麼就可以使用政策模式進行優化:

首先,定義一個抽象政策接口:

public interface Strategy {

    void run() throws Exception;

}
           

然後,進行不同政策的實作:

//Men's strategy implementation class
@Slf4j
public class ManStrategy implements Strategy {

    @Override
    public void run() throws Exception {
        // Fast man's logic
        log.debug("Execute the logic related to men...");
    }

}

//Women's strategy implementation class
@Slf4j
public class WomanStrategy implements Strategy {

    @Override
    public void run() throws Exception {
        // Fast woman's logic
        log.debug("Execute women related logic...");
    }

}

//Others' policy implementation class
@Slf4j
public class OtherStrategy implements Strategy {

    @Override
    public void run() throws Exception {
        // Fast other logic
        log.debug("Perform other related logic...");
    }

}
           

最後,進行政策的應用:

public class StrategyTest {

    public static void main(String[] args) {
        try {
            Strategy strategy = initMap("man");
            strategy.run();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //Initialize the Map to obtain a gender policy
    private static Strategy initMap(String key) {
        //Use simple example
        HashMap<String, Strategy> map = new HashMap<>();
        map.put("man", new ManStrategy());
        map.put("woman", new WomanStrategy());
        map.put("other", new OtherStrategy());
        return map.get(key);
    }

}
           

1.3.4 優劣勢分析及優化

1.3.4.1 劣勢

整體上來看,使用政策模式雖然剔除了大量的if else語句,但是也引入了更多的類檔案,同時在Context中需要維護一個類似系統資料庫的map對象,當增加政策實作時,容易忘記。

優化措施:

在Java中,可以使用函數式程式設計進行優化:

@Slf4j
public class StrategyTest {

    public static void main(String[] args) {
        //Use simple example
        HashMap<String, Strategy> map = new HashMap<>();
        map.put("man", () -> log.debug("Execute the logic related to men..."));
        map.put("woman", () -> log.debug("Execute women related logic..."));
        map.put("other", () -> log.debug("Execute logic related to others..."));

        try {
            map.get("woman").run();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
           

或者,使用枚舉進行優化:

@Slf4j
public enum Strategy {

    //Man state
    MAN(0) {
        @Override
        void run() {
            //Perform related operations
            log.debug("Execute the logic related to men");
        }
    },
    //Woman state
    WOMAN(1) {
        @Override
        void run() {
            //Perform operations related to women
            log.debug("Execute women related logic");
        }
    },
    //Other status
    OTHER(2) {
        @Override
        void run() {
            //Perform other related operations
            log.debug("Perform other related logic");
        }
    };

    abstract void run();

    public int statusCode;

    Strategy(int statusCode) {
        this.statusCode = statusCode;
    }

}
           
public static void main(String[] args) {
        try {
            //Simple use example
            String param = String.valueOf(Strategy.WOMAN);
            Strategy strategy = Strategy.valueOf(param);
            strategy.run();
        } catch (Exception e) {
            e.printStackTrace();
        }
}
           

除此以外,在用戶端實際使用政策時,即對象進行方法的調用時,用戶端必須知道這個政策的所有實作子類,并需要了解這些子類之間的不同以及各自的應用場景,這樣用戶端才能選擇合适的政策實作“确定的行為”。

1.3.4.2 優勢

  • 最直接的好處就是可以讓又臭又長的if else代碼塊看起來更幹淨。
  • 面向對象的三大特點:封裝、繼承、多态,在政策模式中都能找到影子。面向接口程式設計,代碼的可擴充性好
  • 代碼的可測性好,Mock更友善,減少了分支判斷,實作類隻需要各自測試即可。

1.4 Optional

if else分支判斷的很多情況都是進行非空條件的判斷,Optional是Java8開始提供的新特性,使用這個文法特性,也可以減少代碼中if else的數量,例如:

優化前:

String str = "Hello World!";

if (str != null) {
    System.out.println(str);
} else {
    System.out.println("Null");
}
           

優化後:

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

1.5 系統資料庫

這種方式和政策模式有相似之處,但系統資料庫更自由,不需要提煉接口,隻需要将自定義實作在系統資料庫中注冊即可。

例如,優化前:

if (param.equals(value1)) {
    doAction1(someParams);
}else if (param.equals(value2)) {
    doAction2(someParams);
}else if (param.equals(value3)) {
    doAction3(someParams);
}
           

優化後:

//Generic here? For the convenience of demonstration, it can be replaced with the real type we need in actual development
Map<?, Function<?> action> actionMappings = new HashMap<>(); 
// When init
actionMappings.put(value1, (someParams) -> { doAction1(someParams)});
actionMappings.put(value2, (someParams) -> { doAction2(someParams)});
actionMappings.put(value3, (someParams) -> { doAction3(someParams)});
 
// Omit null judgment
actionMappings.get(param).apply(someParams);
           

1.6 責任鍊模式

先來看一段代碼:

public void handle(request) {
    if (handlerA.canHandle(request)) {
        handlerA.handleRequest(request);
    } else if (handlerB.canHandle(request)) {
        handlerB.handleRequest(request);
    } else if (handlerC.canHandle(request)) {
        handlerC.handleRequest(request);
    }
}
           

代碼中也是存在一坨if else語句,但是和上述例子不同之處在于,if條件判斷權在每個handler元件中,每一個handler的判斷方式也可能不盡相同,相當靈活,同一個request可能同時滿足多個if條件

解決方案就是參考開源元件中Filter或者Interceptor責任鍊機制,優化後代碼:

public void handle(request) {
  handlerA.handleRequest(request);
}
 
public abstract class Handler {
    
  protected Handler next;
    
  public abstract void handleRequest(Request request);
    
  public void setNext(Handler next) { this.next = next; }
}
 
public class HandlerA extends Handler {
  public void handleRequest(Request request) {
    if (canHandle(request)) doHandle(request);
    else if (next != null) next.handleRequest(request);
  }
}
           

2.總結&思考

這篇文章主要介紹了代碼中if else代碼塊泛濫時的治理措施,在實際應用時可根據具體場景選擇合理的方案。

其實代碼中存在大面積if else本無問題,用一句網絡流行語來反駁就是:“你就說能不能用吧!”。但是作為有追求的工程師,我們要對項目以及代碼負責,要及時的識别到代碼中的壞味道,并持續重構優化。最後還想說一定要擁抱開源,多研讀他人優秀代碼,并臨摹、思考、實踐,日拱一卒,不期而至。

3.參考

  • https://programmer.ink/think/how-to-optimize-if-there-are-too-many-if-statements-in-java-code-of-series-17.html
  • WikiPedia
  • Quora

作者:京東零售 韓超

來源:京東雲開發者社群

繼續閱讀