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


先来一张镇楼图感受一下 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. 决策表

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,188评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,464评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,562评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,893评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,917评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,708评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,430评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,342评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,801评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,976评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,115评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,804评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,458评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,008评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,135评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,365评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,055评论 2 355