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

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

时间:2024-01-02 11:07:46浏览次数:34  
标签:throw return .. 接手 else user new public

项目会最终沦为屎山,但是作为一个有追求的研发,我们就应当从每个小的if else着手,至少让当前这个项目在你维护期间,让其发展成屎山的速度变慢一些,或者能替之前的老前辈还掉一些技术债才是最好的,各位小伙伴你们觉得呢?

背景

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

小猫:“好,没问题”。

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

接手了个项目,被if..else搞懵逼了_设计模式


是这样的

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


还是这样的

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


平级的if else密密麻麻就算了,但是深套五六层的if else甚至七八层的真的是让人摸不着北。

开启优化

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

接手了个项目,被if..else搞懵逼了_设计模式_04

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

一、提前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://blog.51cto.com/u_13040551/9064668

相关文章

  • 铺先生:明确这些前提更好接手转店,知道越多越好
    明确什么因素对接手转店好?一些想经营店铺的人,其实有两条路可以选择,其一是开店选址经营,其二是通过接手转店来实现。一番比较下来,不难发现其实接手转店更加实惠。明确这些前提更好接手转店!下面小编就来跟大家说一下吧。1. 是否符合需求接手转店必然是为了经营店铺的,而在接手转店之前......
  • 求4,8,16,32....个向量的平均
    m=[[0]*np.array(e).shape[1]foriinrange(np.array(e).shape[0])]j=0ifnp.array(e).shape[0]%2==0:foriinrange(0,int(len(e)/2),2):print('-',i)m[j]=mean_(e[i],e[i+1])j+=1m=np.a......
  • 执行ipmitool命令的时候提示:Could not open device at /dev/ipmi0...
    1、问题:执行ipmitool命令的时候提示“Couldnotopendeviceat/dev/ipmi0or/dev/ipmi/0or/dev/ipmidev/0:Nosuchfileordirectory”2、诊断:缺少相关模块3、解决:更新ipmi的rpm包之后,需要加载模块[root@~]#yuminstallipmi*Loadedplugins:langpacks,produc......
  • 新火种AI|抖音对AIGC亮明牌!AI虚拟人带货或将面临最大危机...
    作者:小岩编辑:小迪12月19日,抖音官方发布了一则重要公告,宣布将严厉打击包括AIGC在内的6种新型违规行为。所谓AIGC,指的就是当下发展势头正劲的生成式人工智能。之所以要打击AIGC,其实是为了重点打击新型养号行为。抖音志在打击黑灰产,而AIGC造假成了绕不过去的“路障”。事实上,抖音此次......
  • 记录--try...catch知识补全
    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助说到try...catch都觉得非常熟悉了,不就是用来捕捉代码块中的错误嘛,平时也用得比较多的。然而因为了解不够多,我的面试却栽在了一个简单的知识点上:try...catch只能捕捉到同步执行代码块中的错误。题目是:以下代码有错......
  • js一道try...catch的面试题
    说到try...catch都觉得非常熟悉了,不就是用来捕捉代码块中的错误嘛,平时也用得比较多的。然而因为了解不够多,我的面试却栽在了一个简单的知识点上:try...catch只能捕捉到同步执行代码块中的错误。题目是:以下代码有错吗?如果有错,应该如何改正?try{setTimeout(()=>{thrown......
  • 进程中的if和else
    进程互斥实验的时候遇到了一段代码没有看懂#include<stdio.h>#include<unistd.h>intmain(){intp1,p2,i;while((p1=fork())==-1);/*创建子进程p1*/if(p1==0){lockf(1,1,0);/*加锁,这里第一个参数为stdout(标准输......
  • Map+函数式接口去掉if-else
    判断条件放在key中对应的业务逻辑放在value中这样子写的好处是非常直观,能直接看到判断条件对应的业务逻辑代码:importcom.wing.service.QueryGrantTypeService;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.web.bind.annotation.P......
  • Map+函数式接口去掉if-else
    判断条件放在key中对应的业务逻辑放在value中这样子写的好处是非常直观,能直接看到判断条件对应的业务逻辑代码:importcom.wing.service.QueryGrantTypeService;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.web.bind.annotation.P......
  • 今年,北美技术行业已有24w人被裁....
    北美tech裁员愈演愈烈!根据Layoffs.fyi的统计,今年已经有1059家科技企业裁员,裁员人数高达24万人。麻了!亚麻裁到了盈利部门亚麻又传出裁员的新消息。让人惊掉下巴的是,这一次竟然向盈利部门—广告部开刀了,可见处境真得很艰难!还不止,有消息称将继续裁减Alexa语音助手业务部门数百个岗位,把......