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的方式引发了我的考虑,由于我也曾在项目代码中引入过如此深重的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.参阅

  • programmer.ink/think/how-t…
  • WikiPedia
  • Quora

作者:京东零售 韩超

来历:京东云开发者社区