首页 > 其他分享 >接手了个项目,被if..else搞懵逼了

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

时间:2023-12-25 14:44:46浏览次数:29  
标签:throw return .. 接手 else user new public

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

 

背景

领导:“这个项目,今后就给你维护了啊,仔细点。”
小猫:“好,没问题”。
可当满怀信心的小猫打开项目工程包翻看一些代码之后,瞬间懵逼没了信心。
蒙圈
是这样的
code2
还是这样的
code1
平级的if else密密麻麻就算了,但是深套五六层的if else甚至七八层的真的是让人摸不着北。

开启优化

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

我们对着上述目录从简单的开始介绍吧:

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

热爱技术,热爱产品,热爱生活,一个懂技术,懂产品,懂生活的程序员~ 更多精彩内容,可以关注公众号“程序员老猫”。 一起讨论技术,探讨一下点子,研究研究赚钱!

标签:throw,return,..,接手,else,user,new,public
From: https://www.cnblogs.com/sexintercourse/p/17926063.html

相关文章

  • 铺先生:接手转店要观察的问题,你知道几个呢?
    不少想开店的朋友在一番权衡利弊之后,都会去选择接手一家转让的门店来实现。但是在接手转店的过程中,你会遇到很多的问题,稍有不慎就会造成不必要的损失,甚至是经营失败!以下这些接手转店要观察的问题,你知道几个呢?1. 看是否要翻修在接手转店的时候,为什么要观察是否存在需要翻修呢?其实一......
  • 我依然没有用quest2串流过一次...
    我在前几年买了quest2在它刚上市的时候,从日本amazon邮过来64gb版本全新没开封是将近2400rmb, 激活quest2于国内是一件蛮麻烦的事,但是毕竟quest2是一个比较便宜的vr头盔,说起来我想要vr头盔这件事,早在2016年我在微软上班的时候就很想了. 那时候vr概念很火,htc的vr已经上市了,......
  • OpenAI Q* 是一个彻底的 谣言...
    OpenAI仍未明确解释Q*究竟是什么,但透露的线索倒是相当不少。11月22日,就在OpenAI决定解雇(后又重新聘用)CEOSamAltman的几天之后,技术媒体TheInformation报道称OpenAI取得了一项重大技术突破,使其能够“开发出更强大的AI模型”。新模型被命名为Q*(音为「Qstar」),“具......
  • 文字超出长度显示...(单行,与多行)
    <!DOCTYPEhtml><html><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width,initial-scale=1.0"><metahttp-equiv="X-UA-Compatible"content=&q......
  • 无涯教程-PL/SQL - nested IF-THEN-ELSE函数
    在PL/SQL编程中,将IF-ELSE语句嵌套通常是合法的,这意味着您可以在其中使用一个IF或ELSEIF语句另一个IF或ELSEIF语句。嵌套IF-THEN-ELSE-语法IF(boolean_expression1)THEN--executeswhenthebooleanexpression1istrueIF(boolean_expression2)......
  • 无涯教程-PL/SQL - IF-THEN-ELSE 语句函数
    在IF-THEN语句序列之后可以是ELSE语句的可选序列,它们在条件为FALSE时执行。IF-THEN-ELSE-语法语法fortheIF-THEN-ELSEstatementis-IFconditionTHENS1;ELSES2;ENDIF;其中,S1和S2是不同的语句序列。在IF-THEN-ELSE语句中,当测试条件为TRUE......
  • mysql报错:Duplicate entry ‘...‘ for key ‘field‘
    错误信息"Duplicateentry'...'forkey'field'"表示在数据库表中,你正在尝试插入一条数据的'number'字段的值已经存在。这通常是由于你设置了'field'字段为唯一键(UNIQUEKEY),而你又尝试插入一个已存在的值。解决这个问题的方法有以下几种:检查输入的数据:确保你插入的数据在该字段......
  • 甘肃捐款名单列表 --- 来源于网络所以...
    小杨哥,捐出了1200万元人民币李佳琦捐款500万元。电商主播李佳琦向中华慈善总会捐赠500万元进行震区专项救援新茶饮公益基金携手古茗、书亦烧仙草、茶百道、蜜雪冰城、沪上阿姨、喜茶、益禾堂、茶颜悦色、7分甜、甜啦啦共计10家新茶饮品牌第一时间响应紧急救援工作,捐赠1000万元......
  • [问题记录] C# 使用NPOI操作Excel模版写入数据 - 生成文件打开时提示 "发现 XXX.xlsx
    解决方案:1.先确保原来的模版文件打开是正常的,没有提示要恢复2.用Office打开这个模版文件,另存为一个文件。用这个文件来作为模版使用。 问题描述:使用C#NPOI操作Excel模版(模版用office打开是正常的),写入数据,导出的文件打开时提示是否尝试恢复,点击“是”后,发现Excel内......
  • 巧妙使用Vue.extend继承组件实现el-table双击可编辑(不使用v-if和v-else)
    问题描述有一个简单的表格,产品要求实现双击可编辑看了一下网上的帖子,大多数都是搞两部分dom,一块是输入框,用于编辑状态填写;另一块是普通标签,用于在不编辑显示状态下呈现单元格文字内容。再加上一个flag标识搭配v-if和v-else去控制编辑状态、还是显示状态。大致代码如下:<el-t......