背景

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

小猫:“好,没问题”。

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



是这样的



还是这样的



平级的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. Llama2-Chinese项目:1-项目介绍和模型推理

    Atom-7B与Llama2间的关系:Atom-7B是基于Llama2进行中文预训练的开源大模型.为什么叫原子呢?因为原子生万物,Llama中文社区希望原子大模型未来可以成为构建AI世界的基础单位.目 ...

  2. 【krpano】密码插件

    密码插件可以在浏览场景或者执行action之前弹出密码输入框,要求用户输入密码.当密码输入成功了才可以进行下一步操作. 下载地址:http://pan.baidu.com/s/1gfOKKKF 给场景 ...

  3. 自定义注解实现数据序列化时进行数据脱敏(基于springboot默认jackjson)、消息转换器HttpMessageConverter

    消息转换器 HttpMessageConverter 消息转化器的作用 将请求报文转化为Java对象 将Java对象转化为响应报文 消息转换器接口 public interface HttpMessa ...

  4. Oracle-判断表上存在高水位线

    表上高水位线:通常一个新建的表,1个8K的数据块存放100行记录,若表上经常插入删除操作,造成表的水位线很高.下面从发现高水位线的办法,及解决高水位的方法说起: 1.发现存在高水位线的表:查看字典表u ...

  5. vscode没有完全汉化怎么办? vs code部分内容没汉化的解决办法

  6. shell脚本之语句(条件、循环)

    条件语句 1.测试 使用[]时要使用空格,注意格式  格式1:test 条件表达式  格式2:[ 条件表达式 ]#注意空格  注意[]空格,否则会失败  测试 是否成功使用 $?返回值来判断  [ 操 ...

  7. Qt 迭代器

    目录 (一) java风格迭代器 1. QListIterator类 1. 初始化 2. findNext() 3. findPrevious() 4. hasNext() 5. hasPreviou ...

  8. SNN_文献阅读_Spiking Deep Convolutional Neural Networks for Energy-Efficient Object Recognition

    两种方法将CNN转化成为SNN: 直接训练一个类似CNN架构的SNN「虽然有类似于STDP等无监督方法,但是处于起步状态」 训练初始的CNN,将训练得到的权重直接应用于类似于CNN架构的SNN「将CN ...

  9. Python 如何实现合并 PDF 文件?

    在处理多个 PDF 文档时,频繁地打开关闭文件会严重影响效率.因此,对于一大堆内容相关的 PDF 文件,我们可以先将这些 PDF 文件合并起来再操作,从而提高工作效率.比如,在传送大量的 PDF 文档 ...

  10. JavaScript高级程序设计笔记06 集合引用类型

    集合引用类型 1. Object(详见c08 p205) 适合存储,在应用程序间交换数据 创建实例: a. 显式构造函数 b. 字面量-->不会调用构造函数(代码更少.更有封装感) 函数:大量参 ...