if过多,if--else过多 ,if--else嵌套过多,优化代码质量

导语:修改异味 Snoar Cognitive Complexity of methods should not be too high
Cognitive Complexity is a measure of how hard the control flow of a method is to understand. Methods with high Cognitive Complexity will be difficult to maintain.
代码检测sonar: 认知的复杂性不应该太高
认知复杂性是衡量一种方法的控制流程理解难度的指标。认知复杂性高的方法难以让别的开发人员去维护。
在项目中遇见此问题那就说明代码质量不高,那么我们就要去优化它(虽然说功能也可实现,有这么一个说法,任何问题都可以通过if-else去解决,如果不能那就是if-else不够多),现实中我们不能让自己代码太冗余,否则你的工资会被打折扣
有以下一些方案供我们学习,我也是学习了他人的文章得到的启示,感谢那些为编码质量做出贡献的作者
参考:1、https://zhuanlan.zhihu.com/p/495845896

解决方案:降低方法内if else, Switch等条件判断复杂度(以下方案难度不一,选择理解其中几种即可)

1. 尽量减少打断线性代码执行的语句(如if else 、for、catch), 多层嵌套语句

2. 用三目运算符替代 if else. 如果没有else分支,不用再特意替换成三目运算符

3.多层嵌套的语句 拆出来,拆成方法,抽取方法(这种常用,易改动,提倡把大方法改成许多小方法的集合)
## 问题一:if…else 过多
方法一:表驱动
对于逻辑表达模式固定的 if...else 代码,可以通过某种映射关系,将逻辑表达式用表格的方式表示;再使用表格查找的方式,找到某个输入所对应的处理函数,使用这个处理函数进行运算。

适用场景

逻辑表达模式固定的 if...else

实现与示例

if (param.equals(value1)) {
    doAction1(someParams);
} else if (param.equals(value2)) {
    doAction2(someParams);
} else if (param.equals(value3)) {
    doAction3(someParams);
}
// ...
可重构为

Map<?, Function<?> action> actionMappings = new HashMap<>(); // 这里泛型 ? 是为方便演示,实际可替换为你需要的类型

// When init
actionMappings.put(value1, (someParams) -> { doAction1(someParams)});
actionMappings.put(value2, (someParams) -> { doAction2(someParams)});
actionMappings.put(value3, (someParams) -> { doAction3(someParams)});

// 省略 null 判断
actionMappings.get(param).apply(someParams);
方法二:职责链模式
介绍

当 if...else 中的条件表达式灵活多变,无法将条件中的数据抽象为表格并用统一的方式进行判断时,这时应将对条件的判断权交给每个功能组件。并用链的形式将这些组件串联起来,形成完整的功能。

适用场景

条件表达式灵活多变,没有统一的形式。

实现与示例

职责链的模式在开源框架的 Filter、Interceptor 功能的实现中可以见到很多。下面看一下通用的使用模式:

重构前:

public void handle(request) {
    if (handlerA.canHandle(request)) {
        handlerA.handleRequest(request);
    } else if (handlerB.canHandle(request)) {
        handlerB.handleRequest(request);
    } else if (handlerC.canHandle(request)) {
        handlerC.handleRequest(request);
    }
}
重构后:

public void handle(request) {
  handlerA.handleRequest(request);
}

public abstract class Handler {
  protected Handler next;
  public abstract void handleRequest(Request request);
  public void setNext(Handler next) { this.next = next; }
}

public class HandlerA extends Handler {
  public void handleRequest(Request request) {
    if (canHandle(request)) doHandle(request);
    else if (next != null) next.handleRequest(request);
  }
}
当然,示例中的重构前的代码为了表达清楚,做了一些类和方法的抽取重构。现实中,更多的是平铺式的代码实现。
方法三:Optional
介绍

Java 代码中的一部分 if...else 是由非空检查导致的。因此,降低这部分带来的 if...else 也就能降低整体的 if...else 的个数。

Java 从 8 开始引入了 Optional 类,用于表示可能为空的对象。这个类提供了很多方法,用于相关的操作,可以用于消除 if...else。开源框架 Guava 和 Scala 语言也提供了类似的功能。

使用场景

有较多用于非空判断的 if...else。

实现与示例

传统写法:

String str = "Hello World!";
if (str != null) {
    System.out.println(str);
} else {
    System.out.println("Null");
}
使用 Optional 之后:

1 Optional<String> strOptional = Optional.of("Hello World!");
2 strOptional.ifPresentOrElse(System.out::println, () -> System.out.println("Null"));
Optional 还有很多方法,这里不一一介绍了。但请注意,不要使用 get() 和 isPresent() 方法,否则和传统的 if...else 无异。
## 问题二:if…else 嵌套过深
问题表现

if...else 多通常并不是最严重的的问题。有的代码 if...else 不仅个数多,而且 if...else 之间嵌套的很深,也很复杂,导致代码可读性很差,自然也就难以维护。

if (condition1) {
    action1();
    if (condition2) {
        action2();
        if (condition3) {
            action3();
            if (condition4) {
                action4();
            }
        }
    }
}
if...else 嵌套过深会严重地影响代码的可读性。当然,也会有上一节提到的两个问题。

如何解决

上一节介绍的方法也可用用来解决本节的问题,所以对于上面的方法,此节不做重复介绍。这一节重点一些方法,这些方法并不会降低 if...else 的个数,但是会提高代码的可读性:

抽取方法
卫语句
方法一:抽取方法
介绍

抽取方法是代码重构的一种手段。定义很容易理解,就是将一段代码抽取出来,放入另一个单独定义的方法。借

用 https://refactoring.com/catalog/extractMethod.html 中的定义:

适用场景

if...else 嵌套严重的代码,通常可读性很差。故在进行大型重构前,需先进行小幅调整,提高其代码可读性。抽取方法便是最常用的一种调整手段。

实现与示例

重构前:

public void add(Object element) {
  if (!readOnly) {
    int newSize = size + 1;
    if (newSize > elements.length) {
      Object[] newElements = new Object[elements.length + 10];
      for (int i = 0; i < size; i++) {
        newElements[i] = elements[i];
      }

      elements = newElements
    }
    elements[size++] = element;
  }
}
重构后:

public void add(Object element) {
  if (readOnly) {
    return;
  }

  if (overCapacity()) {
    grow();
  }

  addElement(element);
}
方法二:卫语句
介绍

在代码重构中,有一个方法被称为“使用卫语句替代嵌套条件语句”https://refactoring.com/catalog/replaceNestedConditionalWithGuardClauses.html。直接看代码:

double getPayAmount() {
    double result;
    if (_isDead) result = deadAmount();
    else {
        if (_isSeparated) result = separatedAmount();
        else {
            if (_isRetired) result = retiredAmount();
            else result = normalPayAmount();
        };
    }
    return result;
}
重构之后

double getPayAmount() {
    if (_isDead) return deadAmount();
    if (_isSeparated) return separatedAmount();
    if (_isRetired) return retiredAmount();
    return normalPayAmount();
}
使用场景

当看到一个方法中,某一层代码块都被一个 if...else 完整控制时,通常可以采用卫语句。
## 问题三:if…else 表达式过于复杂  解决if嵌套过多
主要用代码重构中的抽取方法
引用 https://javaforall.cn/193646.html学习(借鉴学习)
1.分解条件表达式
if (date.before (SUMMER_START) || date.after(SUMMER_END)) { 
   
    charge = quantity * _winterRate + _winterServiceCharge;
} else { 
   
    charge = quantity * _summerRate
}
这种代码很多人可能都觉得没必要去提取方法,但是如果我们想要看懂这段代码,还是必须的去想想才知道在做什么;接下来我们修改一下

if (notSummer(date)) { 
   
    charge = winterCharge(quantity);
} else { 
   
    charge = summerCharge(quantity);
}

private boolean notSummer(Date date){ 
   
    date.before (SUMMER_START) || date.after(SUMMER_END)
}

private double summerCharge(int quantity) { 
   
    return quantity * _summerRate;
}

private double winterCharge(int quantity) { 
   
    return quantity * _winterRate + _winterServiceCharge;
}
2.合并重复的条件判断
double disabilityAmount () { 
   
    if(_seniortiy <2 ) 
        return 0;
    if(_monthsDisabled > 12)
        return 0;
    if(_isPartTime)
        return 0;
    // 省略...
}
这里的条件返回的结果都是一样的,那么我们先把条件合并起来

double disabilityAmount () { 
   
    if(_seniortiy <2 || _monthsDisabled > 12 || _isPartTime) { 
   
        return 0;
    }
    // 省略...
}
接下来我们再来把判断条件判断条件抽取成方法提高可读性

double disabilityAmount () { 
   
    if(isNotEligibleForDisableility()) { 
   
        return 0;
    }
    // 省略...
}

boolean isNotEligibleForDisableility() { 
   
    return _seniortiy <2 || _monthsDisabled > 12 || _isPartTime;
}
举例2:

if(onVacation()) { 
   
    if(lengthOfService() > 10) { 
   
        return 2;
    }
}
return 1;
合并之后的代码

if(onVacation() && lengthOfService() > 10){ 
   
    return 2
}
return 1;
接着我们可以使用三元操作符更加简化,修改后的代码:

return onVacation() && lengthOfService() > 10 ? 2 : 1;
3.提前判断返回
如下语句

if(condition){ 
   
   //dost
}else{ 
   
   return ;
}
改为

if(!condition){ 
   
   return ;
}
//dost
避免一些不必要的分支,让代码更精炼。

4.引入断言工具类
比如下面这段代码:

public void getProjectLimit(String project){ 
   
    if(project == null){ 
   
        throw new RuntimeException("project can not null");
    }
    doSomething();
}
加入Spring的断言后的代码 或者自定义断言 Assert.java

public void getProjectLimit(String project){ 
   
    Assert.notNull(project,"project can not null");
    doSomething();
}
5.善用 Optional(自行百度)
在项目中,总少不了一些非空的判断,可能大部分人还是如下的用法

if(null == user){ 
   
    //action1
}else{ 
   
    //action2
}
Optional,它可以让非空校验更加优雅,间接的减少if操作。

// 如果dtolgetProductType()为空 则默认为0
Integer productType = Optional.ofNullable(dto.getProductType()).orElse(0)

user = Optional.ofNullable(user).orElse(new User());

Optional<User> userOptional = Optional.ofNullable(user);
userOptional.map(action1).orElse(action2);
上面的代码跟第一段是等效的,通过一些新特性让代码更加紧凑。

6.使用枚举
public enum Status { 
   
    NEW(0),RUNNABLE(1),RUNNING(2),BLOCKED(3),DEAD(4);

    public int statusCode;

    Status(int statusCode){ 
   
        this.statusCode = statusCode;
    }
}
那么我们在使用的时候就可以直接通过枚举调用了。
 int statusCode = Status.valueOf(“NEW”).statusCode;
 优雅的解决了下面代码赋值的方式

if(param.equals("NEW")){ 
   
    statusCode = 0;
}else if(param.equals("RUNNABLE")){ 
   
    statusCode = 1;
}
使用枚举优化if else实现2  https://mp.weixin.qq.com/s/GGrsmt2_lBxLNH0aFtCLwg

7.枚举多态
int attackPower(Attacker attacker) { 
   
   return AttackerType.valueOf(attacker.getType()).getAttackPower();
}

enum AttackerType { 
   
    Bartizan("箭塔") { 
   
        @Override
        public int getAttackPower() { 
   
            return 100;
        }
    },
    Archer("弓箭手") { 
   
        @Override
        public int getAttackPower() { 
   
            return 50;
        }
    },
    Tank("坦克") { 
   
        @Override
        public int getAttackPower() { 
   
            return 800;
        }
    };

    private String label;

    Attacker(String label) { 
   
        this.label = label;
    }

    public String getLabel() { 
   
        return label;
    }

    public int getAttackPower() { 
   
        throw new RuntimeException("Can not support the method");
    }
}
8.类多态
if else 示例1:

  String medalType = "guest";
  if ("guest".equals(medalType)) { 
   
      System.out.println("嘉宾勋章");
   } else if ("vip".equals(medalType)) { 
   
      System.out.println("会员勋章");
  } else if ("guard".equals(medalType)) { 
   
      System.out.println("守护勋章");
  }
多态优化:

//勋章接口
public interface IMedalService { 
   
    void showMedal();
}

//守护勋章策略实现类
public class GuardMedalServiceImpl implements IMedalService { 
   
    @Override
    public void showMedal() { 
   
        System.out.println("展示守护勋章");
    }
}
//嘉宾勋章策略实现类
public class GuestMedalServiceImpl implements IMedalService { 
   
    @Override
    public void showMedal() { 
   
        System.out.println("嘉宾勋章");
    }
}

//勋章服务工厂类
public class MedalServicesFactory { 
   

    private static final Map<String, IMedalService> map = new HashMap<>();
    static { 
   
        map.put("guard", new GuardMedalServiceImpl());
        map.put("vip", new VipMedalServiceImpl());
        map.put("guest", new GuestMedalServiceImpl());
    }
    public static IMedalService getMedalService(String medalType) { 
   
        return map.get(medalType);
    }
}
示例2:

int attackPower(Attacker attacker) { 
   
    return attacker.getAttackPower();
}

interface Attacker { 
   
    default int getAttackPower() { 
   
        throw new RuntimeException("Can not support the method");
    }
}

class Bartizan implements Attacker { 
   
    public int getAttackPower() { 
   
        return 100 * getLevel();
    }
}

class Archer implements Attacker { 
   
    public int getAttackPower() { 
   
        return 50 * getLevel();
    }
}

class Tank implements Attacker { 
   
    public int getAttackPower() { 
   
        return 800 * getLevel();
    }
}

9.表驱动法
来自Google的解释:表驱动法是一种编程模式,它的本质是,从表里查询信息来代替逻辑语句(if,case)。

int getMonthDays(int month){ 
   
	switch(month){ 
   
		case 1:return 31;break;
		case 2:return 29;break;
		case 3:return 31;break;
		case 4:return 30;break;
		case 5:return 31;break;
		case 6:return 30;break;
		case 7:return 31;break;
		case 8:return 31;break;
		case 9:return 30;break;
		case 10:return 31;break;
		case 11:return 30;break;
		case 12:return 31;break;
		default:return 0;
	}
}
表驱动法实现方式

int monthDays[12] = { 
   31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int getMonthDays(int month){ 
   
	return monthDays[--month];
}
其实这里的表就是数组而已,通过直接查询数组来获得需要的数据,那么同理,Map之类的容器也可以成为我们编程概念中的表。

Map<?, Function<?> action> actionsMap = new HashMap<>();

// 初试配置对应动作
actionsMap.put(value1, (someParams) -> { 
    doAction1(someParams)});
actionsMap.put(value2, (someParams) -> { 
    doAction2(someParams)});
actionsMap.put(value3, (someParams) -> { 
    doAction3(someParams)});
 
// 省略 null 判断
actionsMap.get(param).apply(someParams);
10. Map + Java8 函数式接口
通过Java8的lambda表达式,我们把需要执行东西存进value中,调用的时候通过匹配key的方式进行。

@Service
public class QueryGrantTypeService { 
   

    @Autowired
    private GrantTypeSerive grantTypeSerive;

    private final Map<String, Function<String, String>> grantTypeMap = new HashMap<>();

    /** * 初始化业务分派逻辑,代替了if-else部分 * key: 优惠券类型 * value: lambda表达式,最终会获得该优惠券的发放方式 */
    @PostConstruct
    public void dispatcherInit() { 
   
        grantTypeMap.put("红包", resourceId -> grantTypeSerive.redPaper(resourceId));
        grantTypeMap.put("购物券", resourceId -> grantTypeSerive.shopping(resourceId));
        grantTypeMap.put("vip会员", resourceId -> grantTypeSerive.vip(resourceId));
    }

    public String getResult(String resourceType, String resourceId) { 
   
        // Controller根据 优惠券类型resourceType、编码resourceId 去查询 发放方式grantType
        Function<String, String> result = grantTypeMap.get(resourceType);
        if (result != null) { 
   
            // 传入 resourceId 执行这段表达式获得String型的grantType
            return result.apply(resourceId);
        }
        return "查询不到该优惠券的发放方式";
    }
}

@Service
public class GrantTypeSerive { 
   

    public String redPaper(String resourceId) { 
   
        //红包的发放方式
        return "每周末9点发放";
    }

    public String shopping(String resourceId) { 
   
        //购物券的发放方式
        return "每周三9点发放";
    }

    public String vip(String resourceId) { 
   
        //qq会员的发放方式
        return "每周一0点开始秒杀";
    }
}
调用:

@RestController
public class GrantTypeController { 
   

    @Autowired
    private QueryGrantTypeService queryGrantTypeService;

    @PostMapping("/grantType")
    public String test(String resourceName){ 
   
        return queryGrantTypeService.getResult(resourceName);
    }
}

原文链接:https://javaforall.cn

策略模式
如何干掉 Spring Boot 中大片的 if else?
业务代码中,太多 if else 怎么办?
设计模式最佳套路—— 愉快地使用策略模式
结束语:
借鉴学习知识,总结供以后查阅,供大家查阅,优化代码质量很重要,关系到你会不会你的上级批斗,关系到你的绩效,随着时间慢慢的也能写好代码

  • 4
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
To reduce the Cognitive Complexity of the method, we can try to simplify its logic and break it down into smaller, more manageable parts. Here are some suggestions: 1. Extract some of the code into separate methods with clear, descriptive names that explain what they do. This can help to make the overall logic of the method easier to understand. 2. Use early exits or guard clauses to reduce the number of nested blocks and simplify the overall flow of the method. 3. Consider using a switch statement or a lookup table to handle different cases or inputs, instead of using multiple if-else blocks. 4. Use meaningful variable names and comments to make the code more readable and understandable. Here is an example of how we could refactor a method to reduce its Cognitive Complexity: ``` public void doSomething(int input) { if (input < 0) { throw new IllegalArgumentException("Input must be non-negative"); } int result = 0; if (input == 0) { // Handle special case where input is zero result = 1; } else if (input == 1) { // Handle special case where input is one result = 1; } else { // Handle all other cases for (int i = 2; i <= input; i++) { result += i; } } System.out.println("Result: " + result); } ``` In this example, we have extracted the special cases into separate if-else blocks, and used an early exit to handle the non-negative input requirement. We have also used a meaningful variable name (`result`) to make the code more readable, and added a comment to explain the purpose of the method.

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值