关于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结构,并在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 clauseguard 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@Slf4jpublic 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@Slf4jpublic class WomanStrategy implements Strategy {    @Override    public void run() throws Exception {        // Fast woman's logic        log.debug("Execute women related logic...");    }}//Others' policy implementation class@Slf4jpublic 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中,可以使用函数式编程进行优化:

@Slf4jpublic 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();        }    }}

或者,使用枚举进行优化:

@Slf4jpublic 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 developmentMap<?, Function<?> action> actionMappings = new HashMap<>(); // When initactionMappings.put(value1, (someParams) -> { doAction1(someParams)});actionMappings.put(value2, (someParams) -> { doAction2(someParams)});actionMappings.put(value3, (someParams) -> { doAction3(someParams)}); // Omit null judgmentactionMappings.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本无问题,用一句网络流行语来反驳就是:“你就说能不能用吧!”。但是作为有追求的工程师,我们要对项目以及代码负责,要及时的识别到代码中的坏味道,并持续重构优化。最后还想说一定要拥抱开源,多研读他人优秀代码,并临摹、思考、实践,日拱一卒,不期而至。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值