现在软件开发领域出现了很多新技术、新概念,但 if...else 这种基本的程序形式并没有发生太大变化。使用好 if...else 不仅对于现在,而且对于将来,都是十分有意义的。今天我们就来看看如何“干掉”代码中的 if...else,还代码以清爽。
问题一:if...else 过多
if...else 过多的代码可以抽象为下面这段代码。其中只列出5个逻辑分支,但实际工作中,能见到一个方法包含10个、20个甚至更多的逻辑分支的情况。另外,if...else 过多通常会伴随着另两个问题:逻辑表达式复杂和 if...else 嵌套过深。对于后两个问题,本文将在下面两节介绍。本节先来讨论 if...else 过多的情况。
if (condition1) { } else if (condition2) { } else if (condition3) { } else if (condition4) { } else { }
接下来我们来看如何解决 if...else 过多的问题。下面我列出了一些解决方法。
1、表驱动;2、职责链模式;3、注解驱动;4、事件驱动;5、有限状态机;6、Optional;7、Assert;8、多态。
3)实现与示例
if (param.equals(value1)) { doAction1(someParams); } else if (param.equals(value2)) { doAction2(someParams); } else if (param.equals(value3)) { doAction3(someParams); } // ...
可重构为
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)}); // 省略 null 判断 actionMappings.get(param).apply(someParams);
还有一些问题,其中的条件表达式并不像上例中的那样简单,但稍加变换,同样可以应用表驱动。下面借用《编程珠玑》中的一个税金计算的例子:
if income <= 2200 tax = 0 else if income <= 2700 tax = 0.14 * (income - 2200) else if income <= 3200 tax = 70 + 0.15 * (income - 2700) else if income <= 3700 tax = 145 + 0.16 * (income - 3200) ...... else tax = 53090 + 0.7 * (income - 102200)
重构前:
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); } }
重构后:
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); } }
从理论角度讲,事件驱动可以看做是表驱动的一种,但从实践角度讲,事件驱动和前面提到的表驱动有多处不同。具体来说:表驱动通常是一对一的关系;事件驱动通常是一对多;表驱动中,触发和执行通常是强依赖;事件驱动中,触发和执行是弱依赖。
Apache Mina 框架,虽然在 IO 框架领域不及 Netty,但它却提供了一个状态机的功能。https://mina.apache.org/mina-project/userguide/ch14-state-machine/ch14-state-machine.html
传统写法:
String str = "Hello World!"; if (str != null) { System.out.println(str); } else { System.out.println("Null"); }
使用 Optional 之后:
Optional<String> strOptional = Optional.of("Hello World!"); strOptional.ifPresentOrElse(System.out::println, () -> System.out.println("Null"));
bob?.department?.head?.name
上一个方法适用于解决非空检查场景所导致的 if...else,类似的场景还有各种参数验证,比如还有字符串不为空等等。很多框架类库,例如 Spring、Apache Commons 都提供了工具里,用于实现这种通用的功能。这样大家就不必自行编写 if...else 了。Apache Commons Lang 中的 Validate 类:https://commons.apache.org/proper/commons-lang/javadocs/api-3.1/org/apache/commons/lang3/Validate.html
Spring 的 Assert 类:https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/Assert.html
问题二:if...else 嵌套过深
if...else 多通常并不是最严重的的问题。有的代码 if...else 不仅个数多,而且 if...else 之间嵌套的很深,也很复杂,导致代码可读性很差,自然也就难以维护。
if (condition1) { action1(); if (condition2) { action2(); if (condition3) { action3(); if (condition4) { action4(); } } } }
上一节介绍的方法也可用用来解决本节的问题,所以对于上面的方法,此节不做重复介绍。这一节重点一些方法,这些方法并不会降低 if...else 的个数,但是会提高代码的可读性:
1、抽取方法
2、卫语句
重构前:
public void add(Object element) { if (!readOnly) { int newSize = size + 1; if (newSize > elements.length) { Object[] newElements = new Object[elements.length + 10]; for (int i = 0; i < size; i++) { newElements[i] = elements[i]; } elements = newElements } elements[size++] = element; } }
重构后:
public void add(Object element) { if (readOnly) { return; } if (overCapacity()) { grow(); } addElement(element); }
直接看代码:
double getPayAmount() { double result; if (_isDead) result = deadAmount(); else { if (_isSeparated) result = separatedAmount(); else { if (_isRetired) result = retiredAmount(); else result = normalPayAmount(); }; } return result; }
重构之后
double getPayAmount() { if (_isDead) return deadAmount(); if (_isSeparated) return separatedAmount(); if (_isRetired) return retiredAmount(); return normalPayAmount(); }
问题三:if...else 表达式过于复杂
if...else 所导致的第三个问题来自过于复杂的条件表达式。下面给个简单的例子,当 condition 1、2、3、4 分别为 true、false,请大家排列组合一下下面表达式的结果。
if ((condition1 && condition2 ) || ((condition2 || condition3) && condition4)) { }
总结
“干掉”if...else 的能力高低反映的是程序员对软件重构、设计模式、面向对象设计、架构模式、数据结构等多方面技术的综合运用能力,反映的是程序员的内功。要合理使用 if...else,不能没有设计,也不能过度设计。这些对技术的综合、合理地运用都需要程序员在工作中不断的摸索总结。