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結構,并在Code Review中被指出了問題,從那以后,我對if else的最大容忍層數就是三層,
我把大量if else的場景按照深度和廣度兩個維度劃分為兩種情況:
-
嵌套層級過深
-
平鋪范圍太廣
下面就討論一下,當代碼中存在大量這樣結構的代碼的時候,該如何優化?
1.解決方案
1.1 盡早回傳
又稱衛陳述句,即Guard Statement
WikiPedia:
In computer programming, aguardis abooleanexpressionthat must evaluate to true if the program execution is to continue in the branch in question.
Regardless of which programming language is used, aguard clause,guard code, orguard statement, is a check of integritypreconditionsused 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 foridempotence(so subsequent calls are nops), as in thedispose pattern. The guard provides anearly exitfrom asubroutine, and is a commonly used deviation fromstructured programming, removing one level of nesting and resulting in flatter code:[1]replacing
if guard { ... }withif 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
作者:京東零售 韓超
來源:京東云開發者社區
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/553976.html
標籤:其他
下一篇:返回列表
