少年,请理性消灭if-else

在平时的实践生产中,伴随着业务逻辑越来越复杂,if-else的滥用的越来越厉害。当然其并不影响代码的逻辑使用,至于其可读性那只能是仁者见仁智者见智。就我个人而言,我是很反感在代码中使用if的判空和逻辑判断。除非是业务紧急或者实在是想不出好的办法才会使用if-else, 毕竟无脑使用是绝对没问题的。

本文代码地链接

1. 编码规范

编码规范能够简化if-else的使用,是基于JDK所提供的基本能力实现的,同是能够很好避免满屏的if-else

1.1 三目运算符

    public GenderEnum getGender(int genderValue){
        if(genderValue == GenderEnum.FEMALE.getValue()){
            return GenderEnum.FEMALE;
        }else {
            return GenderEnum.MALE;
        }
    }

======================================== After Change =============================
      public GenderEnum getGenderAfterChange(int genderValue){
        return genderValue == GenderEnum.FEMALE.getValue()? GenderEnum.FEMALE:GenderEnum.MALE;
    }

虽然三目运算符是一个很基本的逻辑判断方式,但是在公司中使用的人太少了,基本上上来就是if-else。有时候要是能看到代码中有三目运算符都会眼前一亮,得看看git提交记录是哪位写的。另外三目运算符还可以解决Lambda中的final定义字段的问题。如下:

   public void test(){
        int genderValue = 0;
        GenderEnum genderEnum = null;
        if(genderValue == GenderEnum.FEMALE.getValue()){
            genderEnum = GenderEnum.FEMALE;
        }else {
            genderEnum = GenderEnum.MALE;
        }
/******************************************************************************************************************/
       final GenderEnum genderEnumFinal = genderValue == GenderEnum.FEMALE.getValue() ? GenderEnum.FEMALE: GenderEnum.MALE;
    }

在上述的代码中,变量genderEnum是不可以用于lambda中,而genderEnumFinal是可以用于lambda表达式的,所以很好的解决了不可变的问题。

1.2 Optional的应用

Optional是JDK8推出的新用法,虽然整个类的代码很少,但是很大程度上解决了判空的问题。

没有Optional的情况

if( null != person && null != person.getTeacher()){
    return person.getTeacher().getName();
}else{
    return "";
}
==========================================================================
Optional的使用

   return Optional.ofNullable(person)
                  .map(Person::getTeacher)
                  .map(Teacher::getName)
                  .orElse("");

1.3自定义lambda表达式

//1.定义一个函数式接口
@FunctionalInterface
public interface BooleanFunction<T> {
    T handleCondition(Supplier<? super T> ifFunction, Supplier<? super T> elseFunction);
}

//2.写一个Boolean工具类进行判断
public class BooleanUtil<T> {

    //支持多个boolean结果, 相当于 或 条件
    public static BooleanFunction isAnyMatch(Boolean... booleans) {
        boolean result = Arrays.stream(booleans).filter(Objects::nonNull).anyMatch(Boolean::booleanValue);
        return getBooleanFunction(result);
    }
    //支持多个boolean结果, 相当于 与 条件
    public static BooleanFunction isAllMatch(Boolean... booleans){
        boolean result = Arrays.stream(booleans).filter(Objects::nonNull).allMatch(Boolean::booleanValue);
        return getBooleanFunction(result);
    }

    //支持多个boolean的lambda表达式结果, 相当于 与 条件
    public static BooleanFunction isAllMatch(BooleanSupplier... booleanSuppliers){
        boolean result = Arrays.stream(booleanSuppliers).filter(Objects::nonNull).map(BooleanSupplier::getAsBoolean).allMatch(Boolean::booleanValue);
        return getBooleanFunction(result);
    }

    //支持多个boolean的lambda表达式结果, 相当于 或 条件
    public static BooleanFunction isAnyMatch(BooleanSupplier... booleanSuppliers) {
        boolean result = Arrays.stream(booleanSuppliers).filter(Objects::nonNull).map(BooleanSupplier::getAsBoolean).anyMatch(Boolean::booleanValue);
        return getBooleanFunction(result);
    }

    private static BooleanFunction getBooleanFunction(boolean result){
        return (ifFunction, elseFunction) -> result ? ifFunction.get() : Optional.ofNullable(elseFunction).map(Supplier::get).orElse(null);
    }

    public static void main(String[] args) {

       String result =  (String)BooleanUtil.isAnyMatch( () -> StringUtils.equals("hh","hhe")).handleCondition(
                () -> "if function",
              null
        );
        System.out.println(result); //null

        String result1 =  (String)BooleanUtil.isAnyMatch(StringUtils.equals("hh","hhe")).handleCondition(
                () -> "if function",
                () -> "else function"
        );
        System.out.println(result1); //else function
    }
}

自定义的函数接口让if-else的逻辑像写文章一样连贯,当然这种观点也是仁者见仁智者见智,喜欢写if-else的守旧派绝对会认为这就是换汤不换药的改变,十分鸡肋。所以这种优化方式看个人,最主要还得看leader。如果你用了别人看不习惯,认为维护很麻烦当然认为你的方式是有问题的。甚至一些保守的公司连lambda的都不允许使用,如果你所在的公司是这样的话,我还是劝你尽早离职,毕竟公司会影响你个人发展的前景。因为你以后提出一些新颖的想法绝对会被守旧的模式所否定,如果一次又一次的被否定后会让你的大脑停滞思考一些新颖的方法。

2.逻辑拆分

2.1拆分方法


Person person = new Person();
 if(null == id) {
     person.setId("");
     person.setDomainName("empty");
 }else {
     person.setId(id);
     person.setDomainName(id + "domainName");
 }
return person;

==========================================================================================================

Person person = new Person();
person.setId(id);
person.setDomainName(id + "domainName");
if(null == id){
    person.setId("");
    person.setDomainName("empty");
}
return person;

上述是将else中的逻辑抽了出来,先做了一个塞值操作,后置的if判断逻辑如果需要执行会对之前的赋值进行覆盖。在生产实践中,需要自行判断if 和else中需要执行的频次,通常情况下,id为null的很少会被执行的,所以此处将else中的逻辑放上去;如果id为null执行频次很高,就要求把if中的逻辑提取出来,然后之前的else逻辑变成if即可。

2.2 else逻辑提前return

if (null != id) {
    Person person = testService.getById(id);
    if (null != person) {
        return person.getId();
    }
    return null;
} else {
    return null;
}
===============================================================================================================

if(null == id){
    return null;
}
Person person = testService.getById(id);
if(null == person){
    return null;
}
return person.getId();

将不需要执行的数据进行提前return,减少if-else逻辑,让代码看起来更加清爽,逻辑清晰。

3.设计模式

//根据角色类型做出相应的操作

    public void doActionByRole(String role){
        if(role.equals(RoleEnum.HEADMASTER_ROLE)){
            System.out.println("校长审作业");
        }else if(role.equals(RoleEnum.TEACHER_ROLE)){
            System.out.println("老师改作业");
        }else if(role.equals(RoleEnum.STUDENT_ROLE)){
            System.out.println("学生写作业");
        }else {
            System.out.println("");
        }
    }

3.1工厂模式

//使用工厂模式实现

//1.定义顶层接口
public interface Role {
    void doAction();
}
//2.每一个角色实现接口

public class HeadermasterRole implements Role{
    @Override
    public void doAction() {
        System.out.println("校长审作业");
    }
}

public class StudentRole implements Role {
    @Override
    public void doAction() {
        System.out.println("学生写作业");
    }
}

public class TeacherRole implements Role {
    @Override
    public void doAction() {
        System.out.println("老师改作业");
    }
}

//3.提供工厂方法,根据role返回对应的role对象
public class RoleFactory {
    private static final Map<String, Role> ROLE_MAP = ImmutableMap.<String, Role>builder()
            .put(RoleEnum.TEACHER_ROLE.getValue(), new TeacherRole())
            .put(RoleEnum.STUDENT_ROLE.getValue(), new StudentRole())
            .put(RoleEnum.HEADMASTER_ROLE.getValue(), new HeadermasterRole())
            .build();

    public static Role getRole(String name) {
        return ROLE_MAP.getOrDefault(name, null);
    }
}

//4.运行demo
public class Demo {

    public static void main(String[] args) {
        //工厂模式实现
        RoleFactory.getRole("student").doAction();
    }
}

工厂模式对开闭原则十分友好,如果以后需要增加一些新的角色,只需要实现一些新的接口,代码中的逻辑不会发生改变。

开闭原则:对扩展开放,对修改关闭

3.2策略模式(不能解决)

//1.提供策略的类, 根据不同的策略对象执行对应方法
public class RoleStrategy {
    private final Role role;

    public RoleStrategy(Role role) {
        this.role = role;
    }

    public void doActionByRole() {
        role.doAction();
    }
}

//2.运行demo
public class Demo {

    public static void main(String[] args) {
        //策略模式实现
        if(role.equals(RoleEnum.HEADMASTER_ROLE.getValue())){
            new RoleStrategy(new HeadermasterRole());
        }else if(role.equals(RoleEnum.TEACHER_ROLE.getValue()){
            new RoleStrategy(new TeacherRole())
        }else if(role.equals(RoleEnum.STUDENT_ROLE.getValue())){
            new RoleStrategy(new StudentRole())
        }
    }
}

单纯的策略模式实际上就只有以上内容,实际上是不能解决if-else的逻辑;而工厂模式之所以能解决关键是含有Role对象的Map,根据roleName可以映射到相应的对象进而实现消除if-else的效果。

3.3 枚举类+策略


//定义枚举类型
public enum NameEnum {

    ZHANG_SAN("张三") {
        public void doAction() {
            System.out.println("我是张三");
        }
    }, LI_SI("李四") {
        @Override
        public void doAction() {
            System.out.println("我是李四");
        }
    }, WANG_WU("王五") {
        @Override
        public void doAction() {
            System.out.println("我是王五");
        }
    }, ZHAO_QI("赵七") {
        @Override
        public void doAction() {
            System.out.println("我是赵七");
        }
    }, TIAN_BA("田八") {
        @Override
        public void doAction() {
            System.out.println("我是田八");
        }
    };

    //声明一个Map
    private static final Map<String, NameEnum> NAME_ENUM_MAP = Collections.unmodifiableMap(ImmutableMap.<String, NameEnum>builder()
            .put(ZHANG_SAN.getValue(), ZHANG_SAN)
            .put(LI_SI.getValue(), LI_SI)
            .put(WANG_WU.getValue(), WANG_WU)
            .put(ZHAO_QI.getValue(), ZHAO_QI)
            .put(TIAN_BA.getValue(), TIAN_BA)
            .build());

    private final String name;

    NameEnum(String name) {
        this.name = name;
    }

    public String getValue() {
        return this.name;
    }

    //定义策略方法
    public abstract void doAction();


    public static void main(String[] args) {
        NAME_ENUM_MAP.get("田八").doAction();  // 输出: 我是田八
    }
}

实际上述你也可以重新定义一个接口,然后枚举类实现该接口,这样枚举类中的每一个枚举都要实现接口中的策略方法,也能达到相同的效果。由于我觉得重新定义一个接口很没必要,所以在枚举类中定义了一个抽象方法,实际上达到的效果是一样的。其实主要结果if-else的关键还是构建的枚举Map,通过map拿到对应的枚举然后执行对应的策略。

4.总结

实际开发中,我们应该尽量避免多重if-else的嵌套使用,当然我们不要强行的消除if-else,一定要保持理性,需要消除才去消除。其实if-else的滥用我总结有以下两点:1.开发迭代周期过快,开发人员来不及思考;2.开发人员不想思考;3.前人写了很多if-else,后人继续堆屎山。