背景

领导:“这个项目,今后就给你维护了啊,仔细点。”

小猫:“好,没问题”。

可当满怀信心的小猫打开项目工程包翻看一些代码之后,瞬间懵逼没了信心。



是这样的



还是这样的



平级的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着手,至少让当前这个项目在你维护期间,让其发展成屎山的速度变慢一些,或者能替之前的老前辈还掉一些技术债才是最好的,各位小伙伴你们觉得呢?

接手了个项目,被if..else搞懵逼了的更多相关文章

  1. vue项目 一行js代码搞定点击图片放大缩小

    一行js代码搞定xue项目需要点击图片放大缩小,其实主要用的是用到了vue:class的动态切换,内容比较简单.一开始我把维护的需求想得太复杂了,和测试小姐姐聊了一下才反应过来. 两个月不到跟了四个项 ...

  2. 简化 Spring Boot 项目部署,Flyway 搞起来

    虽然我之前录了一个微人事(https://github.com/lenve/vhr)部署视频(新版微人事部署教程来啦),但是由于这次升级涉及到了 Redis 和 RabbitMQ,所以在本地跑微人事还 ...

  3. C# 一个基于.NET Core3.1的开源项目帮你彻底搞懂WPF框架Prism

    --概述 这个项目演示了如何在WPF中使用各种Prism功能的示例.如果您刚刚开始使用Prism,建议您从第一个示例开始,按顺序从列表中开始.每个示例都基于前一个示例的概念. 此项目平台框架:.NET ...

  4. 【项目实践】一文带你搞定Spring Security + JWT

    以项目驱动学习,以实践检验真知 前言 关于认证和授权,R之前已经写了两篇文章: [项目实践]在用安全框架前,我想先让你手撸一个登陆认证 [项目实践]一文带你搞定页面权限.按钮权限以及数据权限 在这两篇 ...

  5. 七天接手react项目-起步

    七天接手react项目-起步 背景 假如七天后必须接手一个 react 项目(spug - 一个开源运维平台),而笔者只会 vue,之前没有接触过 react,此刻能做的就是立刻展开一个"7 ...

  6. 七天接手react项目 系列 —— react 脚手架创建项目

    其他章节请看: 七天接手react项目 系列 react 脚手架创建项目 前面我们一直通过 script 的方式学习 react 基础知识,而真实项目通常是基于脚手架进行开发. 本篇首先通过 reac ...

  7. 七天接手react项目 系列

    七天接手react项目 背景 假如七天后必须接手一个 react 项目(spug - 一个开源运维平台),而笔者只会 vue,之前没有接触过 react,此刻能做的就是立刻展开一个"7天 r ...

  8. 七天接手react项目 系列 —— react 路由

    其他章节请看: 七天接手react项目 系列 react 路由 本篇首先讲解路由原理,接着以一个基础路由示例为起点讲述路由最基础的知识,然后讲解嵌套路由.路由传参,最后讲解路由组件和一般组件的区别,以 ...

  9. 项目管理心得:一个项目经理的个人体会、经验总结(zz)

    本人做项目经理工作多年,感到做这个工作最要紧的就是要明白什么是因地制宜.因势利导,只有最合适的,没有什么叫对的,什么叫错的,项目经理最忌讳 的就是完美主义倾向,尤其是做技术人员出身的,喜欢寻找标准答案 ...

  10. Beta阶段项目展示博客

    Beta阶段项目展示 团队成员的简介 详细见团队简介 角色 姓名 照片 项目经理,策划 游心 策划 王子铭 策划 蔡帜 美工 赵晓宇 美工 王辰昱 开发.架构师 解小锐 开发 陈鑫 开发 李金奇 开发 ...

随机推荐

  1. 基于velero及minio实现etcd数据备份与恢复

    1.Velero简介 Velero 是vmware开源的一个云原生的灾难恢复和迁移工具,它本身也是开源的,采用Go语言编写,可以安全的备份.恢复和迁移Kubernetes集群资源数据:官网https: ...

  2. 第1章 Git概述

    第1章 Git概述 Git 是一个免费的.开源的分布式版本控制系统,可以快速高效地处理从小型到大型的各种项目. Git 易于学习,占地面积小,性能极快. 它具有廉价的本地库,方便的暂存区域和多个工作流 ...

  3. 算法打卡|Day5 哈希表part01

    哈希表 part01 今日任务 ● 哈希表理论基础 ● 242.有效的字母异位词 ● 349. 两个数组的交集 ● 202. 快乐数 ● 1. 两数之和 目录 哈希表 part01 链表理论基础 Pr ...

  4. 探索Lighthouse性能分数计算背后的奥秘

    作为开发我们都知道,页面性能很重要,一个性能良好的页面可以给用户带来非常好的用户体验.那么,怎么能知道自己写的页面性能是好是坏呢? Lighthouse 是Chrome提供给开发者用来测量页面性能的工 ...

  5. SSM(Spring+SpringMVC+MyBatis)框架集成

    引言 进行SSM(Spring+SpringMVC+MyBatis)集成的主要原因是为了提高开发效率和代码可维护性.SSM是一套非常流行的Java Web开发框架,它集成了Spring框架.Sprin ...

  6. 起风了,NCC 云原生项目孵化计划

    时间回到 2016 年,彼时 .NET Core 1.0 刚刚发布 1.0 版本,我跟几位好友共同发起 .NET Core 中文学习组(.NET Core China Studying Group)和 ...

  7. StackOverflow 并不只是一个问答网站

    首页    新文章  联系  管理  订阅  StackOverflow 并不只是一个问答网站 今天看到了一个网站的模仿StackOverflow的问答应用,有点儿感慨:是不是设计这个模仿应用的人,真 ...

  8. C#学习笔记--复杂数据类型、函数和结构体

    C#基础 复杂数据类型 特点:多个数据变量地一个集合体,可以自己命名 种类:枚举.数组和结构体 枚举:整型常量的集合 数组:任意变量类型的顺序存储的数据集合 结构体:任意变量类型的数据组合成的数据块 ...

  9. 【第一章 web入门】afr_3——模板注入与proc文件夹

    [第一章 web入门]afr_3--模板注入与proc文件夹 题目来源n1book,buu上的环境 看题 url中提供了name参数,类似在路径中进行了文件名查询然后展示: 随便输入一个数字: 说明肯 ...

  10. Windows下音视频对讲演示程序(声学回音消除、噪音抑制、语音活动检测、自动增益控制、自适应抖动缓冲)(2023年07月13日更新)

    Windows下音视频对讲演示程序 必读说明 简介   本软件根据<道德经>为核心思想而设计,实现了两个设备之间进行音视频对讲,一般可用于楼宇对讲.智能门铃对讲.企业员工对讲.智能对讲机. ...