共计 6973 个字符,预计需要花费 18 分钟才能阅读完成。
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 abooleanexpression)that 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]#cite_note-beck-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
作者:京东批发 韩超
起源:京东云开发者社区