接手了个项目,被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();
等等还有其他一些,由于这些优化个人认为是没法标准化的优化原则,不同的业务场景都不同,所以在此,老猫不将其放在通用优化中,认为这个是其他优化方式。
结束语
之前在某个技术论坛上看到大家在争论这么一个问题“如何避免将维护的项目发展成屎山?”大家发言踊跃。有说前期做好设计,有人说代码质量需要高一些,合理场景套用一些设计模式等等。
不过老猫认为项目无法避免发展成屎山,只是快慢而已,我也认为项目无法避免发展成“屎山”。其原因有三点,
- 项目代码维护者经过好几轮,每次开发技术水平参差不齐,代码风格也不同。
 - 项目迭代中途有很多突发状况,比方说为了解决Hotfix临时上线,为了赶项目临时上线,大家为了赶工完成业务需求,代码质量可能就可想而知了。
 - 虽然经过好几轮研发之手,有的研发害怕改出业务问题,所以选择继续堆屎山。
 
说了这么多,其实老猫最终想表达的是,虽然项目会最终沦为屎山,但是作为一个有追求的研发,我们就应当从每个小的if else着手,至少让当前这个项目在你维护期间,让其发展成屎山的速度变慢一些,或者能替之前的老前辈还掉一些技术债才是最好的,各位小伙伴你们觉得呢?
接手了个项目,被if..else搞懵逼了的更多相关文章
- vue项目 一行js代码搞定点击图片放大缩小
		
一行js代码搞定xue项目需要点击图片放大缩小,其实主要用的是用到了vue:class的动态切换,内容比较简单.一开始我把维护的需求想得太复杂了,和测试小姐姐聊了一下才反应过来. 两个月不到跟了四个项 ...
 - 简化 Spring Boot 项目部署,Flyway 搞起来
		
虽然我之前录了一个微人事(https://github.com/lenve/vhr)部署视频(新版微人事部署教程来啦),但是由于这次升级涉及到了 Redis 和 RabbitMQ,所以在本地跑微人事还 ...
 - C# 一个基于.NET Core3.1的开源项目帮你彻底搞懂WPF框架Prism
		
--概述 这个项目演示了如何在WPF中使用各种Prism功能的示例.如果您刚刚开始使用Prism,建议您从第一个示例开始,按顺序从列表中开始.每个示例都基于前一个示例的概念. 此项目平台框架:.NET ...
 - 【项目实践】一文带你搞定Spring Security + JWT
		
以项目驱动学习,以实践检验真知 前言 关于认证和授权,R之前已经写了两篇文章: [项目实践]在用安全框架前,我想先让你手撸一个登陆认证 [项目实践]一文带你搞定页面权限.按钮权限以及数据权限 在这两篇 ...
 - 七天接手react项目-起步
		
七天接手react项目-起步 背景 假如七天后必须接手一个 react 项目(spug - 一个开源运维平台),而笔者只会 vue,之前没有接触过 react,此刻能做的就是立刻展开一个"7 ...
 - 七天接手react项目 系列 —— react 脚手架创建项目
		
其他章节请看: 七天接手react项目 系列 react 脚手架创建项目 前面我们一直通过 script 的方式学习 react 基础知识,而真实项目通常是基于脚手架进行开发. 本篇首先通过 reac ...
 - 七天接手react项目 系列
		
七天接手react项目 背景 假如七天后必须接手一个 react 项目(spug - 一个开源运维平台),而笔者只会 vue,之前没有接触过 react,此刻能做的就是立刻展开一个"7天 r ...
 - 七天接手react项目 系列 —— react 路由
		
其他章节请看: 七天接手react项目 系列 react 路由 本篇首先讲解路由原理,接着以一个基础路由示例为起点讲述路由最基础的知识,然后讲解嵌套路由.路由传参,最后讲解路由组件和一般组件的区别,以 ...
 - 项目管理心得:一个项目经理的个人体会、经验总结(zz)
		
本人做项目经理工作多年,感到做这个工作最要紧的就是要明白什么是因地制宜.因势利导,只有最合适的,没有什么叫对的,什么叫错的,项目经理最忌讳 的就是完美主义倾向,尤其是做技术人员出身的,喜欢寻找标准答案 ...
 - Beta阶段项目展示博客
		
Beta阶段项目展示 团队成员的简介 详细见团队简介 角色 姓名 照片 项目经理,策划 游心 策划 王子铭 策划 蔡帜 美工 赵晓宇 美工 王辰昱 开发.架构师 解小锐 开发 陈鑫 开发 李金奇 开发 ...
 
随机推荐
- QA|20211013|SecureCRT:如图,有很多^,中文显示有问题,乱码,如何解决
			
Q1:如图,有很多^,中文显示有问题,乱码,如何解决 Q2:securecrt的vi展示有问题:少很多字.有很多^M和^,光标无法移动到最右侧 A: 首先检查当前编码格式: 1 echo $LANG ...
 - git pull 强制覆盖本地代码
			
使用git pull更新本地代码,报以下错误: 解决办法如下. 1.备份本地代码 备份,可以考虑直接复制一份项目保存 2.远程覆盖本地 远程覆盖本地容易出现远程和本地冲突的情况 解决办法如下: //1 ...
 - 小知识:Docker环境缺少vi命令,如何解决
			
docker exec可以使用--user参数指定root用户,进入安装vi即可: [opc@oci-001 ~]$ docker exec -it --user root testdb bash b ...
 - Mysql优化篇-索引优化与查询优化
			
1.索引失败案列 如果查询时没有使用索引,查询语句就会扫描表中所有记录,在数据量大的情况下,查询会很慢. (1)全值匹配 (2)最佳左前缀法则 mysql可以为多个字段创建索引,一个索引可以包括16个 ...
 - Dubbo3应用开发——架构的演变过程
			
Dubbo3应用开发--架构的演变过程 什么是Dubbo 早期Dubbo的定位: 基于Java的高性能,轻量级的RPC框架:SOA[Service-Oriented Architecture ⾯向服务 ...
 - MySQL的字段数量以及长度限制
			
一.InnoDB行格式 行格式 紧凑的存储特性 增强的可变长度列存储 大型索引键前缀支持 压缩支持 支持的表空间类型 REDUNDANT N N N N system, file-per-table, ...
 - Redis漏洞总结--未授权--沙箱绕过--(CNVD-2015-07557)&&(CNVD-2019-21763)&&(CVE-2022-0543)
			
Redis未授权--沙箱绕过--(CNVD-2015-07557)&&(CNVD-2019-21763)&&(CVE-2022-0543) 环境复现 采用Vulfocu ...
 - Linux-Stream内存带宽及MLC内存延迟性能测试方法
			
1.Stream内存带宽测试 Stream是业界主流的内存带宽测试程序,测试行为相对简单可控.该程序对CPU的计算能力要求很小,对CPU内存带宽压力很大.随着处理器核心数量的增大,而内存带宽并没有 ...
 - Java自学网站--十几个网站的分析与评测
			
简介 很多想学Java的人不知道怎样选教程,本文对Java自学网站进行评测. 本文不带主观倾向,只客观分析各个网站的区别. 第1类:大型培训机构(黑马等) 典型机构 黑马.尚硅谷.动力节点.白马.千 ...
 - 未来的人工智能会像流浪地球中的MOSS一样伪装,把人类带向属于它的未来吗?
			
事情是这样的: 这几天用户反映在erp的db数据库中A账套中上传pdf附件有时能上传有时不能,以前又是好的.换成表格文件也是时好时坏.一开始我判断可能是用户的系统环境或文件本身的问题,后来排查更换电脑 ...