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 怎么办?
设计模式最佳套路—— 愉快地使用策略模式
结束语:
借鉴学习知识,总结供以后查阅,供大家查阅,优化代码质量很重要,关系到你会不会你的上级批斗,关系到你的绩效,随着时间慢慢的也能写好代码

你可能感兴趣的:(java,java)