【Java后台开发规范】--- 圈复杂度

前言

做Java开发的,大多数可能都有看过阿里的Java后台开发手册,里面有关于Java后台开发规范的一些内容,基本覆盖了一些通用、普适的规范,但大多数都讲的比较简洁,本文主要会用更多的案例来对一些规范进行解释,以及结合自己的经验做补充!

其他类型的规范

【Java后台开发规范】— 不简单的命名
【Java后台开发规范】— 日志的输出
【Java后台开发规范】— 线程与并发
【Java后台开发规范】— 长函数、长参数
【Java后台开发规范】— 设计原则
【Java后台开发规范】— Null值处理
【Java后台开发规范】— 异常的处理

圈复杂度

圈复杂度是一种衡量代码复杂度的标准,其数量上表现为独立路径的条数,也可以理解为覆盖所有的可能情况最少使用的测试用例个数,圈复杂度高说明程序代码的判断逻辑复杂,可能质量低,且难于测试和维护。

在sonar规范中,经常有关于圈复杂度的扫描,比如一个方法的圈复杂度不得超过5或者10。

一般来说,代码里每出现一次if、for、while、case、catch等这样的语句,圈复杂度就加1。

很明显,圈复杂度的目的就是为了减少代码里控制语句的滥用,比如像这样:

在这里插入图片描述

接下来我们就来看看如何可以减少圈复杂度。

提炼方法

这个和之前提到的长函数类似,如何存在通用的方法可以提炼出来,一些条件控制语句多数也可以提炼出来,总之就是一个大函数变多个小函数。

if、else

一提到if、else的优化必然会想到策略模式,策略模式主要解决的就是在多种算法相似的情况下,使用if、else所带来的高复杂度,不过除了使用策略模式之外,还有一些其他的小技巧,我相信在平时开发过程中无论是有意识还是无意识,应该都有用到过,下面我们就一起来看看。

卫语句

第一种方式就使用卫语句。

这是很正常的逻辑,嵌套了if控制语句

public void test() {
    if(条件1成立){
        if(条件2成立){
            执行xxx逻辑;
        }
    }
}

优化后,嵌套消失了

public void test() {
    if(!条件1成立){
        return;
    }
    if(!条件2成立){
        return;
    }
    执行xxx逻辑;
}

这是一种经典的重构方法:通过卫语句来替代嵌套的条件表达式

减少了嵌套,也就减少了层次的缩进,这样在阅读理解上会更加轻松,减少了代码的复杂度。

去else
public void test() {
    if (10 < amount && amount < 20) {
        执行xxx逻辑;
    } else if (21 < amount && amount < 30) {
        执行xxx逻辑;
    } else if (31 < amount && amount < 40) {
        执行xxx逻辑;
    } else {
        执行xxx逻辑;
    }
}

去除else后

public void test1() {
    if (10 < amount && amount < 20) {
        执行xxx逻辑;
        return;
    }
    if (21 < amount && amount < 30) {
        执行xxx逻辑;
        return;
    }
    if (31 < amount && amount < 40) {
        执行xxx逻辑;
        return;
    }
    执行xxx逻辑;
}

这也是一种常见的减少复杂层级的方式。

策略模式

最后我们再来看看策略模式的方法。

下面这段逻辑描述的是,如果memberAttr是VIP,我们就根据用户VIP级别给到不同的折扣。

public BigDecimal test(String memberAttr) {
    BigDecimal amount = BigDecimal.valueOf(10d);
    if ("VIP".equals(memberAttr)) {
        String level = getVipLevel();
        if ("1".equals(level)) {
            return amount.multiply(BigDecimal.valueOf(0.9));
        }
        if ("2".equals(level)) {
            return amount.multiply(BigDecimal.valueOf(0.8));
        }
        return amount.multiply(BigDecimal.valueOf(0.7));
    }
    return amount;
}

这是最简单的实现方式。

卫语句优化

public BigDecimal test(String memberAttr, String userId) {
    BigDecimal amount = BigDecimal.valueOf(10d);
    if (!"VIP".equals(memberAttr)) {
        return amount;
    }
    String level = getVipLevel(userId);
    if ("1".equals(level)) {
        amount = amount.multiply(BigDecimal.valueOf(0.9));
    } else if ("2".equals(level)) {
        amount = amount.multiply(BigDecimal.valueOf(0.8));
    } else {
        amount = amount.multiply(BigDecimal.valueOf(0.7));
    }
    return amount;
}

去else

public BigDecimal test(String memberAttr, String userId) {
    BigDecimal amount = BigDecimal.valueOf(10d);
    if (!"VIP".equals(memberAttr)) {
        return amount;
    }
    String level = getVipLevel(userId);
    if ("1".equals(level)) {
        return amount.multiply(BigDecimal.valueOf(0.9));
    }
    if ("2".equals(level)) {
        return amount.multiply(BigDecimal.valueOf(0.8));
    }
    return amount.multiply(BigDecimal.valueOf(0.7));
}

策略模式

public BigDecimal method1(String memberAttr, String userId) {
    BigDecimal amount = BigDecimal.valueOf(10d);
    if (!"VIP".equals(memberAttr)) {
        return amount;
    }
    String level = getVipLevel(userId);
    DiscountStrategy discountStrategy = DiscountStrategyFactory.getDiscountStrategy(level);
    return discountStrategy.discount(amount);
}

switch

switch其实就是if、else的一种简化写法,在语法上给与了优化,但要注意如果switch的条件出现在过多的业务场景中,那么可能就需要优化了。

刚才的方法,我们可以换成switch写法

public BigDecimal test(String memberAttr, String userId) {
    BigDecimal amount = BigDecimal.valueOf(10d);
    if (!"VIP".equals(memberAttr)) {
        return amount;
    }
    String level = getVipLevel(userId);
    switch (level) {
        case "1":
            return amount.multiply(BigDecimal.valueOf(0.9));
        case "2":
            return amount.multiply(BigDecimal.valueOf(0.8));
        default:
            return amount.multiply(BigDecimal.valueOf(0.7));
    }
}

假设现在又出现了一种超级VIP,那么它可能是这样的。

public BigDecimal test(String memberAttr, String userId) {
    BigDecimal amount = BigDecimal.valueOf(10d);
    if (!"VIP".equals(memberAttr)) {
        return amount;
    }
    String level = getSuperVipLevel(userId);
    switch (level) {
        case "1":
            return amount.multiply(BigDecimal.valueOf(0.8));
        case "2":
            return amount.multiply(BigDecimal.valueOf(0.7));
        default:
            return amount.multiply(BigDecimal.valueOf(0.6));
    }
}

折扣力度发生了变化,但是也是根据用户级别来区分的,如果再出现一些根据用户级别来进行业务逻辑处理的场景,那我们就应该考虑抽象、多态的方式了,否则一处变动,你需要找到所有使用switch的地方,并修改它。

循环嵌套

for循环嵌套不太好优化,因为可能会和选择的算法有关,比如下面这个经典的求两个数组交集。

第一版,for循环嵌套,复杂度高,性能也差,最不应该采取的方式。

public int[] intersection(int[] nums1, int[] nums2) {
    Set<Integer> intersectionSet = new HashSet<>();
    for (int i = 0; i < nums1.length; i++) {
        for (int j = 0; j < nums2.length; j++) {
            if (nums1[i] == nums2[j]) {
                intersectionSet.add(nums1[i]);
            }
        }
    }
    return intersectionSet.stream().mapToInt(Integer::valueOf).toArray();
}

第二版,借用API,循环嵌套没了,使用retainAll函数实现交集,性能优于第一版。

public static int[] intersection1(int[] nums1, int[] nums2) {
    Set<Integer> set1 = new HashSet<>();
    Set<Integer> set2 = new HashSet<>();
    for(int num : nums1){
        set1.add(num);
    }
    for(int num : nums2){
        set2.add(num);
    }
    set1.retainAll(set2);
    return set1.stream().mapToInt(Integer::valueOf).toArray();
}

第三版,利用contains函数,实际上就是利用O(1)时间复杂度的hash函数,性能上要优于第二版,但圈复杂度确实上升了。

public static int[] intersection2(int[] nums1, int[] nums2) {
    Set<Integer> set1 = new HashSet<>();
    Set<Integer> intersectionSet = new HashSet<>();
    for(int num : nums1){
        set1.add(num);
    }
    for(int num : nums2){
        if(set1.contains(num)){
            intersectionSet.add(num);
        }
    }
    return intersectionSet.stream().mapToInt(Integer::valueOf).toArray();
}

第四版,排序+双指针,这是最优解的算法,不过复杂度大大上升。

public static int[] intersection3(int[] nums1, int[] nums2) {
    Arrays.sort(nums1);
    Arrays.sort(nums2);
    int p1 = 0, p2 = 0, index = 0;
    int[] nums = new int[nums1.length + nums2.length];
    while (p1 < nums1.length && p2 < nums2.length) {
        if (nums1[p1] == nums2[p2]) {
            if (index == 0 || nums1[p1] != nums[index - 1]) {
                nums[index++] = nums1[p1];
            }
            p1++;
            p2++;
        } else if (nums1[p1] < nums2[p2]) {
            p1++;
        } else {
            p2++;
        }
    }
    return Arrays.copyOfRange(nums, 0, index);
}

其他建议

尽量不要在条件判断中附加其他的业务逻辑,条件判断尽量清晰明了。

if(长的高 && 长的帅 && 有钱){
	...
}

boolean 高富帅 = 长的高 && 长的帅 && 有钱
if(高富帅){
	...
}

尽量避免反逻辑

if(x < 100){
	...
}

if(!x >= 100){
	...
}

// 两者都表示当x<100时,执行,但反逻辑让人理解起来更麻烦。

长行代码学会换行

StringBuilder sb = new StringBuilder();
sb.append("a").append("b").append("c").append("d").append("e");

// "."号换行
sb.append("a").append("b")
        .append("c")
        .append("d")
        .append("e");


new User(String userName, String account, String age, String sex, String phone, String email)

// ","号换行
new User(String userName, String account, String age
         , String sex, String phone, String email)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码拉松

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值