IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 【Java后台开发规范】--- 圈复杂度 -> 正文阅读

[Java知识库]【Java后台开发规范】--- 圈复杂度

前言

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

其他类型的规范

【Java后台开发规范】— 不简单的命名
【Java后台开发规范】—日志的输出
【Java后台开发规范】—线程与并发
【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)

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-11-30 15:28:34  更:2021-11-30 15:30:28 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 4:52:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码