背景

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

小猫:“好,没问题”。

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



是这样的



还是这样的



平级的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. vue3 甘特图(一):选择与初始化甘特图

    vue3 甘特图(一) 1.功能使用背景: 甘特图是一种项目管理工具,以图形直观的方式显示项目的时间轴和任务计划,为了可扩展和定制相关任务的开发,故此选择dhtmlx-gantt 2.vue3 初始化 ...

  2. 《Python魔法大冒险》006 变量的迷雾

    小鱼和魔法师走了很久,终于来到了一个神秘的森林前.这片森林与众不同,它被一层厚厚的迷雾所包围,仿佛隐藏着无尽的秘密. 小鱼好奇地看着这片森林:"这是什么地方?" 魔法师:这是魔法森 ...

  3. Oracle为表添加约束

    转载自:https://blog.csdn.net/qq_38662525/article/details/94192475 创建一个学生表和院系表:院系表为主表,学生表为从表   create ta ...

  4. 使用MySQL存储过程提高数据库效率和可维护性

    MySQL 存储过程是一种强大的数据库功能,它允许你在数据库中存储和执行一组SQL语句,类似于编程中的函数.存储过程可以大幅提高数据库的性能.安全性和可维护性.本文将详细介绍MySQL存储过程的使用. ...

  5. dedebiz实时时间调用

    {dede:tagname runphp='yes'}@me = date("Y-m-d H:i:s", time());{/dede:tagname}

  6. 基于 Wiki.js 搭建知识库系统

    前言 本文介绍如何使用 Wiki.js 搭建知识库系统. Wiki.js 官网 安装 前提准备 Wiki.js 几乎可以在任何支持 Node.js 的系统上运行.它可以运行在 Linux .Windo ...

  7. Solution -「九省联考 2018」劈配

    Description Link. 一年一度的综艺节目<中国新代码>又开始了.Zayid 从小就梦想成为一名程序员,他觉得这是一个展示自己的舞台,于是他毫不犹豫地报名了. 轻车熟路的 Za ...

  8. JVM面试题、关键原理、JMM

    boolean:占用1个字节,取值为true或false. byte:占用1个字节,范围为-128到127. short:占用2个字节,范围为-32,768到32,767. int:占用4个字节,范围 ...

  9. 整理php防注入和XSS攻击通用过滤

    对网站发动XSS攻击的方式有很多种,仅仅使用php的一些内置过滤函数是对付不了的,即使你将filter_var,mysql_real_escape_string,htmlentities,htmlsp ...

  10. 其它——MyCat实现分库分表

    文章目录 MyCat实现分库分表 一 开源数据库中间件-MyCat 二 MyCat简介 三 MyCat下载及安装 3.1 MySQL安装与启动 3.2使用docker启动多个数据库 3.3 MyCat ...