布景

领导:“这个项目,今后就给你维护了啊,仔细点。” 小猫:“好,没问题”。 可当满怀信心的小猫翻开项目工程包翻看一些代码之后,瞬间懵逼没了信心。

接手了个项目,被if..else搞懵逼了
是这样的

接手了个项目,被if..else搞懵逼了
仍是这样的

接手了个项目,被if..else搞懵逼了
平级的if else密密麻麻就算了,可是深套五六层的if else甚至七八层的真的是让人摸不着北。

敞开优化

那么就上面小猫遇到的这种情况,面对着几代程序员精心堆积的屎山,试问尊下该怎么应对?不慌,老猫罗列了以下处理方案,假如各位还有比较好的优化办法也欢迎留言。

接手了个项目,被if..else搞懵逼了

咱们对着上述目录从简略的开端介绍吧:

一、提早return法

当咱们遇到空目标或许有部分满足条件之后才干履行的时分,不要只想着正向逻辑,其实能够逆向思想,把不满足条件的优先排除掉。这样能够有效防止if else的深嵌套。 优化前代码:

if(condition){
//doSomething
}else{
}
return;

优化后如下:

if(!condition){
  return;
}

二、能省则省,规避终究的else

原来的代码:

public Result addUser() {
	if (StrUtil.equals(userStatus, "online")) {
	    return doStep1();
	} else {
		return doStep2();
	}
	// else 后面没有其他事务时,可省掉终究的else,使代码简练
}

优化后的代码:

public Result addUser() {
	if (StrUtil.equals(userStatus, "online")) {
      return doStep1();
	}
  return doStep2();
}

当然这里边要注意的点是,一定要确认是终究的else,并没有其他的事务逻辑。

三、 三目运算符

仍是基于上面的代码,假如只有两种事务的话,其实在一个办法里边直接用三目运算法进行履行即可。如下改造:

public Result addUser() {
	 return StrUtil.equals(userStatus, "online")) ?doStep1() : doStep2();
}

一个办法一行代码搞定。

四、运用optional

许多事务场景下,其实咱们写if 是为了判空,自从java8之后其实多了一个Optional神器,Optional 是个容器,它能够保存类型 T 的值,或许仅仅保存null。Optional 供给了许多办法,这样咱们就不用显式进行空值检测。Optional 类的引进很好的处理空指针异常。咱们看下下面的优化办法: 代码优化前:

if (user == null) {
    throw new Exception("未查询到用户信息");
}
if (user != null) {
    update(user); // 履行办法调用
}

代码优化后:

Optional.ofNullable(user).orElseThrow(() -> new Exception("未查询到用户信息"));
Optional.ofNullable(user).ifPresent(user -> update(user));

隐式调用相当优雅。

五、规划形式优化法

规划形式优化法其实也是针对不同的场景运用不同的规划形式从而简化多余的if else。

第一种,合理运用职责链形式。

咱们再详细结合一种场景,比方说现在页面上有新注册的用户,他需求提交相关的身份信息进行认证,此刻,咱们底层往往会对他提交的信息做相关的校验处理。 底层咱们的校验办法(1)需求验证基本字非空性 (2)需求验证身份信息根底字段合法性 (2)需求调用第三方进行要素认证。 原始代码如下:

public void addUser(User user) {
	// 1.非空校验
	if (StrUtil.isBlank(user.getUsername())) {
		throw new RuntimeException("用户名为空!");
	}
	if (StrUtil.isBlank(user.getPassword())) {
		throw new RuntimeException("密码为空!");
	}
	...
	// 2.格局校验
	if (!ValidUtil.isIdCardNo(user.getIdCardNo())) {
		throw new RuntimeException("身份证号格局过错!");
	}
	if (!ValidUtil.isEmail(user.getEmail())) {
		throw new RuntimeException("手机号格局过错!");
	}
	if (!ValidUtil.isEmail(user.getEmail())) {
 		throw new RuntimeException("邮箱格局过错!");
	}
   	...
	// 3.要四素认证校验
  if(!doFourStampVerify(User user)){
 		throw new RuntimeException("四要素认证失利!");
  }
}

此处或许还有许多其他的省掉的场景。所以单个文件中的If else或许比幻想中多的多。那么咱们怎么用职责链形式进行优化呢? 改造代码如下,首要界说一个处理器接口:

/**
 * 处理器链接口
 */
public interface UserChainHandler {
    void handler(User user);
}

剩余不同的场景校验只要去完结这个接口就能够了,不过需求界说好顺序

@Component
@Order(1) // 指定注入顺序
public class UserParamNullValidChainHandler implements UserChainHandler {
    @Override
    public void handler(User user) {
	   	// 1.非空校验
	    if (StrUtil.isBlank(user.getUsername())) {
			throw new RuntimeException("用户名为空!");
		}
		if (StrUtil.isBlank(user.getPassword())) {
			throw new RuntimeException("密码为空!");
		}
}
@Component
@Order(1) // 指定注入顺序
public class UserParamNullValidChainHandler implements UserChainHandler {
    @Override
    public void handler(User user) {
	   	// 1.非空校验
	    if (StrUtil.isBlank(user.getUsername())) {
			throw new RuntimeException("用户名为空!");
		}
    ...
}
/**
 * 格局校验处理器
 */
@Component
@Order(2) // 指定注入顺序
public class UserParamFormatValidChainHandler implements UserChainHandler {
    @Override
    public void handler(User user) {
	    // 2.格局校验
		if (!ValidUtil.isIdCardNo(user.getIdCardNo())) {
			throw new RuntimeException("身份证号格局过错!");
		}
    ...
}
/**
 * 四要素处理器
 */
@Component
@Order(3) // 指定注入顺序
public class FourElementVerifyChainHandler implements UserChainHandler {
    @Override
    public void handler(User user) {
	    // 2.格局校验
		if (!doFourStampVerify(User user)) {
			throw new RuntimeException("四要素认证失利!");
		}
}
//进行拼装
@Component
@RequiredArgsConstructor
public class UserChainContext {
    private final List<UserChainHandler> userChainHandlerList; // 自动注入职责链处理器
    /**
     * 职责链组件履行
     *
     * @param requestParam 请求参数
     */
    public void handler(User user) {
        // 此处依据 Ordered 实际值进行排序处理
        userChainHandlerList.forEach(x -> x.handler(user));
    }
}

终究咱们的原来的add办法进行这样调用就好了

public void addUser(User user) {
	// 履行职责链
	userChainContext.handler(user);
}

第二种,合理运用战略形式+工厂形式。

假定咱们遇到这样一个场景,咱们目前底层是一个会员体系,目前体系需求核算各种会员套餐的价格,然后套餐的详细形式主要是由上层体系传递指定给咱们。假如只关注事务直接撸代码的话,应该是如下。

public Result calcPrice(CalcPriceParam calcPriceParam){
  //判断对应的核算价格的场景
  Integer type = judgeType(calcPriceParam);
  //依据场景调用不同的办法 ,主张更好的编码习惯是把type改成枚举类型哈~
  if(type == 1){
    return calcPriceForTypeOne();
  }
  if(type == 2){
    return calcPriceForTypeTwo();
  }
  if(type == 3){
    return calcPriceForTypeThree();
  }
  .....
  if(typr == 10){
    return calcPriceForTypeTen();
  }
}

显而易见随着会员价格场景套餐越来越多,咱们的if也会越来越多。 可是假如运用战略形式的话,咱们能够做到如下:

public interface Strategy {
  Result calcPrice(CalcPriceParam calcPriceParam);
  int getBizType();
}
@Service
public Class firstStragy implement Strategy {
  Result calcPrice(CalcPriceParam calcPriceParam) {
    ....
    return result;
  }
  int getBizType() {
    return 1;
  }
}
public Class secondStragy implement Strategy {
  Result calcPrice(CalcPriceParam calcPriceParam) {
    ....
    return result;
  }
  int getBizType() {
    return 2;
  }
}
@Service
public class StrategyContext{
  Map<Integer,CalcPriceInterface> strategyContextMap = new HashMap<>();
  //注入对应的战略类
  @Autowired
  Strategy[] strategys;
  @PostConstruct
  public void setStrategyContextMap(){
    for(Stragegy stragegy:strategys){
        strategyContextMap.put(stragegy.getCode,stragegy);
    }
  }
  //依据场景调用不同的办法 
  public Result calcPrice(CalcPriceParam calcPriceParam){
  	Integer type = judgeType(calcPriceParam);
    CalcPriceInterface calcPriceInstance = strategyContextMap.get(type);
    return calcPriceInstance.calcPrice(calcPriceParam);
  }
}

这样一来,咱们上面的第一个办法中的If else的完结将会变得很简略,如下:

@Autowired
StrategyContext strategyContext;
public Result calcPrice(CalcPriceParam calcPriceParam){
		strategyContext.calcPrice(calcPriceParam);
}

这样即使新增新的核算形式,咱们只需去完结Strategy接口而且重写里边两个办法即可完结后续事务的拓宽。代码优雅简略,可维护性强。 以上便是用规划形式针对大量if else进行改造。

六、表驱动法

这种办法个人觉得有点像战略形式,可是又不需求单独抽出相关类去承载注册办法,而是简略地将办法通过函数式的办法放到Map中,比及需求运用的时分再进行调用。 原始烂代码,咱们仍是参考上述会员费用金额核算的场景。咱们能够进行如下办法优化:

Map<String, Function<?> action> actionMap = new HashMap<>();
action.put("type1",() -> {calcPriceForTypeOne()});
action.put("type2",() -> {calcPriceForTypeTwo()});
action.put("type3",() -> {calcPriceForTypeThree()});
...
// 运用
actionMap.get(action).apply();

当然假如想要再优化得好一些的话,能够进行接口抽取,然后进行完结,在此不打开,留下给小伙伴们思考一下。

七、其他场景灵活运用,干掉if else

咱们再回到之前小猫遇到的那两个代码截图,其实咱们能够看到有个大量if else并排的代码其实主要是想要比较相关的特点有没有发生改变,假如发生改变,那么则回来false,没有改变则回来true。其实咱们想想是不是能够通过重写LogisticDO这个目标的equals办法来进行完结呢?这样是不是也规避了大量的if else。

还有其他一些当然也是依据详细场景来处理,比方说,我需求依据不同的type类型,进行获取不同的描述信息,那么此刻咱们是不是能够运用enum去维护呢? 如下:

if(status.equals(1)){
   return "订单未付出";
}else if(status.equals(2)){
   return "订单已付出"
}else if(status.equals(3)){
   return "订单已发货"
}
.....

优化后

@Getter
@AllArgsConstructor
public enum OrderStatusEnum {
    UN_PAID("1","订单未付出"),
    PAIDED("2","订单已付出"),
    SENDED("3","订单已发货"),
    .....;
    private String status;
    private String statusDes;
    static OrderStatusEnum of(String status) {
        for (OrderStatusEnum statusEnum : OrderStatusEnum.values()) {
            if (statusEnum.getStatus().equals(status)) {
                return statusEnum;
            }
        }
        return null;
    }
}
String orderStatusDes = OrderStatusEnum.of(orderStatus).getStatusDes();

等等还有其他一些,由于这些优化个人以为是无法标准化的优化准则,不同的事务场景都不同,所以在此,老猫不将其放在通用优化中,以为这个是其他优化办法。

结束语

之前在某个技术论坛上看到我们在争论这么一个问题“怎么防止将维护的项目开展成屎山?”我们讲话踊跃。有说前期做好规划,有人说代码质量需求高一些,合理场景套用一些规划形式等等。 不过老猫以为项目无法防止开展成屎山,仅仅快慢罢了,我也以为项目无法防止开展成“屎山”。其原因有三点,

  1. 项目代码维护者通过好几轮,每次开发技术水平参差不齐,代码风格也不同。
  2. 项目迭代中途有许多突发状况,比方说为了处理Hotfix暂时上线,为了赶项目暂时上线,我们为了赶工完结事务需求,代码质量或许就可想而知了。
  3. 尽管通过好几轮研制之手,有的研制害怕改出事务问题,所以选择继续堆屎山。

说了这么多,其实老猫终究想表达的是,尽管项目会终究沦为屎山,可是作为一个有追求的研制,咱们就应当从每个小的if else着手,至少让当时这个项目在你维护期间,让其开展成屎山的速度变慢一些,或许能替之前的老前辈还掉一些技术债才是最好的,各位小伙伴你们觉得呢?