signed

QiShunwang

“诚信为本、客户至上”

如何解决代码中过多的 if else ?

2020/8/20 11:55:08   来源:


先来一张镇楼图感受一下 if else 的魔法吧。

image.png

一、由一个几百行 if 引发的思考

有个场景,50张字典表,需要为其他服务提供一个统一的接口来校验用户输入的字典表 id 是否合法。

校验逻辑已经很清晰了,根据参数选择对应的表校验 id 是否存在。

if("table_a".equals(table)) {
      // check id
    }
    if("table_b".equals(table)) {
      // check id
    }
    if("table_c".equals(table)) {
      // check id
    }...

再加上参数校验,函数调用,@Autowired bean 等等,一坨几百行的代码 ok 了。再新加表再加 if else 就行了,😋 完美。

如此,N 年后另一个可怜的小伙伴就看到这坨东西。

二、KO 这些 if else

回想上面的场景,实际上就是要根据表名去确定 id 是否存在表中,那么只要将表名与操作对应起来就行了。故而采用哈希表的形式,将表名与操作对应起来。部分代码如下:

// 用于保存表与 Function 的对应关系 
 private final Map<String, Function<Object, Object>> actionMappings = new ConcurrentHashMap<>(50);

 @PostConstruct
 private void init() {
    // map 初始化
    actionMappings.put(TableConstants.TABLE_A, (params) -> tableAManager.getById(params));
  }

/**
 * 校验逻辑
 *
 *@param table
 *@param id
 */
 public boolean valid(String table, Long id) {
    Object object = actionMappings.get(table).apply(id);
    // 不存在则校验失败
    return !Objects.isNull(object);
 }

如此,N 多行 if 被消除了,这种编程方式也叫做表驱动。虽然 if 没有了,但是在初始化 actionMappings 的时候还是很多行重复代码。下面采用注解方式解决:

/**
 * 标记此注解的 bean 会加入基础数据校验全局 Function Map
 *
 * @author aysaml
 * @date 2020/5/7
 */
@Documented
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidHandler {

  TABLE_ENUM value();
}

value 是表名枚举,在需要的类上面加上此注解即可。同时定义一个 context 用来专门存储 actionMappings 。

/**
 * 数据校验上下文对象,用于保存各表的 Function Map
 *
 * @author aysaml
 * @date 2020/5/7
 */
@Component
public class CommonDataValidContext {

  private static final Logger LOGGER = LoggerFactory.getLogger(CommonDataValidContext.class);

  private final Map<String, Function<Object, Object>> actionMappings = new ConcurrentHashMap<>(50);

  /**
   * 方法加入 mappings
   *
   * @param model 表名
   * @param action 方法
   */
  public void putAction(String model, Function<Object, Object> action) {
    if (!Objects.isNull(action)) {
      actionMappings.put(model, action);
      LOGGER.info(
          "[{}] add to CommonDataValidContext actionMappings, actionMappings size : {}",
          model,
          actionMappings.size());
    }
  }

  /**
   * 执行方法获取返回结果
   *
   * @param model
   * @param param
   * @return
   */
  public <P, R> R apply(String model, P param) {
    if (actionMappings.containsKey(model)) {
      return (R) actionMappings.get(model).apply(param);
    } else {
      LOGGER.error("执行数据校验时model={}不存在!", model);
      throw new RuntimeException("基础数据校验时发生错误:" + model + "表不存在!");
    }
  }

  /**
   * 判断 mappings 中是否含有给定 model 的处理方法
   *
   * @param model
   * @return
   */
  public boolean containsKey(String model) {
    return actionMappings.containsKey(model);
  }

  /**
   * 校验执行方法的返回值是否为空
   *
   * @param model
   * @param param
   * @param <P>
   * @return
   */
  public <P> boolean validResultIsNull(String model, P param) {
    return Objects.isNull(this.apply(model, param));
  }
}

然后通过监听器的方式,将含有 ValidHandler 注解的方法加入 actionMappings 。

/**
 * 基础数据校验处理方法监听器
 *
 * @author aysaml
 * @date 2020/5/7
 */
@Component
public class CommonValidActionListener implements ApplicationListener<ContextRefreshedEvent> {

  @Override
  public void onApplicationEvent(ContextRefreshedEvent event) {
    Map<String, Object> beans =
        event.getApplicationContext().getBeansWithAnnotation(ValidHandler.class);
    CommonDataValidContext commonDataValidContext =
        event.getApplicationContext().getBean(CommonDataValidContext.class);
    beans.forEach(
        (name, bean) -> {
          ValidHandler validHandler = bean.getClass().getAnnotation(ValidHandler.class);
          commonDataValidContext.putAction(
              validHandler.value().code(),
              (param) -> {
                try {
                  return bean.getClass().getMethod("getById", Long.class).invoke(bean, param);
                } catch (Exception e) {
                  e.printStackTrace();
                }
                return null;
              });
        });
  }
}

三、更多消除 if else 的方法。

1. 提前return

这样可以使代码在逻辑表达上会更清晰,如下:

if (condition) {
 // do something
} else {
  return xxx;
}

按照逆向思维来,优化如下:

if (!condition) {
  return xxx;
} 
// do something

还有一种常见的傻瓜编程(如有冒犯,敬请见谅,对码不对人🙏 ):

if(a > 0) {
      return true;
    } else {
      return false;
    }

话不多说了,直接 return a > 0; 不香吗?

2. 策略模式

简单来说就是根据不同的参数执行不同的业务逻辑。 如下:

if (status == 0) {
  // 业务逻辑处理 0
} else if (status == 1) {
  // 业务逻辑处理 1
} else if (status == 2) {
  // 业务逻辑处理 2
} else if (status == 3) {
  // 业务逻辑处理 3
}...

优化如下:

  • 多态
interface A {
  void run() throws Exception;
}

class A0 implements A {
    @Override
    void run() throws Exception {
        // 业务逻辑处理 0
    }
}

class A1 implements A {
    @Override
    void run() throws Exception {
        // 业务逻辑处理 1
    }
}
// ...

然后策略对象存放在一个 Map 中,如下:

A a = map.get(param);
a.run();

2.2 枚举

public enum Status {
    NEW(0) {
      @Override
      void run() {
        //do something  
      }
    },
    RUNNABLE(1) {
      @Override
       void run() {
         //do something  
      }
    };

    public int statusCode;

    abstract void run();

    Status(int statusCode){
        this.statusCode = statusCode;
    }
}

重新定义策略枚举

public enum Aenum {
    A_0 {
      @Override
      void run() {
        //do something  
      }
    },
    A_1 {
      @Override
       void run() {
         //do something  
      }
    };
    //...
    abstract void run();
}

通过枚举优化之后的代码如下

Aenum a = Aenum.valueOf(param);
a.run();

3. Java 8 的 Optional

Optional主要用于非空判断,是 Java 8 提供的新特性。

使用之前:

if (user == null) {
    //do action 1
} else {
    //do action2
}

如果登录用户为空,执行action1,否则执行action 2,使用Optional优化之后,让非空校验更加优雅,间接的减少if操作

Optional<User> userOptional = Optional.ofNullable(user);
userOptional.map(action1).orElse(action2);

4. 决策表

就是上面的表驱动编程方法。

欢迎访问个人博客 获取更多知识分享。