烂怂if-else代码优化方案

if,else,代码优化,方案 · 浏览次数 : 83

小编点评

**3.参考文章 https://programmer.ink/think/how-to-optimize-if-there-are-too-many-if-statements-in-java-code-of-series-17.htmlWikiPediaQuora作者:京东零售 韩超来源:京东云开发者社区。归纳总结以上内容,生成内容时需要带简单的排版** **3.优化建议** * **使用函数式编程**可以减少代码中if else的数量。 * **使用枚举**可以减少代码中多个if条件的判断。 * **使用注册表**可以减少代码中重复的判断。 * **使用责任链模式**可以优化代码中多个handler的处理顺序。 * **使用开源组件**可以减少代码中的重复代码。

正文

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语句,如下:
image.png

这里并非评论这段代码写法有问题,因为我还并没有深入到项目细节之中,可能这已经是多轮优化的结果嘞。

但是这个多层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]replacingif 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.参考

作者:京东零售 韩超

来源:京东云开发者社区

与烂怂if-else代码优化方案相似的内容:

烂怂if-else代码优化方案

这篇文章主要介绍了代码中if else代码块泛滥时的治理措施,在实际应用时可根据具体场景选择合理的方案。

一个混乱千万级软件项目

背景:公司接到一个亿级的项目,软件大概占到1/4的比例,整个项目包含了硬件和软件团队。软件团队是要实是一个软件产品,让其控制各种硬件设备做自动化运作,并打通上下游系统的数据。软件同时统计分析(包括机器学习和AI) 整个项目设备的运作和任务执行情况,服务于后续运营优化。 项目成员结构:大项目经理,对这

松灵机器人scout mini小车 自主导航(3)——建图导航仿真

松灵机器人Scout mini小车建图导航仿真 在之前的文章中,我们已经介绍了如何在gazebo和rviz对scout mini小车进行仿真,并且测试了添加自定义的传感器,在本文章中将进一步介绍如何利用scout mini小车 在仿真环境中建图和导航。 仓库链接: https://gitee.com

松灵机器人scout mini小车 自主导航(2)——仿真指南

松灵机器人Scout mini小车仿真指南 之前介绍了如何通过CAN TO USB串口实现用键盘控制小车移动。但是一直用小车测试缺乏安全性。而松灵官方贴心的为我们准备了gazebo仿真环境,提供了完整的仿真支持库,本文将介绍如何上手使用仿真。 官方仓库地址:https://github.com/ag

orangepi zero2在linux5.4以上内核使用ili9341

背景 根据orangepi zero2用户手册说明,linux5.13内核不能使用 modprobe fbtft_device 驱动spi lcd 查看linux内核源码提交记录,发现在v5.4-rc3中删除了fbtft_device.c文件 commit如下 staging/fbtft: Remo

掌握把“烂”SQL牢牢关进笼子里的密钥

摘要:本文通过5个部分内容帮助开发者快速了解GaussDB(DWS) 资源管理机制,让数仓过载烦恼不再,把“烂”SQL牢牢关进笼子里。 本文分享自华为云社区《直播回顾 | 掌握把“烂”SQL牢牢关进笼子里的密钥》,作者: 华为云社区精选 。 混合负载场景下,怎样避免“烂”语句对数据库系统的冲击?如何

从头到尾说一次 Spring 事务管理(器)

事务管理,一个被说烂的也被看烂的话题,还是八股文中的基础股之一。​本文会从设计角度,一步步的剖析 Spring 事务管理的设计思路(都会设计事务管理器了,还能玩不转?)

通过实战操作学git

虽然说 ”好记性不如烂笔头”,但是学习不看等于没学,学习不用等于不会,所以说”实战才是检验真理的唯一标准“,通过实战则会学到很多东西。 因为陈** 太懒,并且不喜欢查百度,老是犯同样的问题,于是我通过完整的操作git流程和一些实战中的场景,将常用git流程和命令整理了下来,这样也方便我女盆友带入学习

GaussDB技术解读系列之SQL Audit,面向应用开发的SQL审核工具

开发者的技术能力良莠不齐,DBA对数据库知识的局限性导致烂SQL无处不在,而且随着数据库的不断变更或演进,一些好的SQL也可能逐步变成需要优化的烂SQL, 我们要时刻不断地找寻它们的踪迹。

LLaMA 3 源码解读-大语言模型5

title: llama3源码解读 tags: DL 本来不是很想写这一篇,因为网上的文章真的烂大街了,我写的真的很有可能没别人写得好。但是想了想,创建这个博客就是想通过对外输出知识的方式来提高自身水平,而不是说我每篇都能写得有多好多好然后吸引别人来看。那作为对整个合集内容的完善,这篇博客会解析现在