业务场景: 当前我们项目在发布流水线的时候,codecheck 圈复杂度高于10的,或者14的,需要进行一些整改,
什么是圈复杂度?
圈复杂度(Cyclomatic complexity,CC)也称为条件复杂度,是一种衡量代码复杂度的标准,其符号为V(G)。
圈复杂度(Cyclomatic complexity)是一种代码复杂度的衡量标准,在1976年由Thomas J. McCabe, Sr. 提出,目标是为了指导程序员写出更具可测性和可维护性的代码。
它可以用来衡量一个模块判定结构的复杂程度,数量上表现为独立路径条数,也可以理解为覆盖所有可能的情况最少需要的测试用例数量。
程序的可能错误和高的圈复杂度有着很大关系,圈复杂度最高的模块和方法,其缺陷个数也可能最多。
圈复杂度大说明程序代码的判断逻辑复杂,可能质量低,且难于测试和维护。
圈复杂度的计算方法
圈复杂度有两种计算方法:点边计算法和节点判定法。
节点判定法
圈复杂度的计算还有另外一种更直观的方法,因为圈复杂度所反映的是“判定条件”的数量,所以圈复杂度实际上就是等于判定节点的数量再加上1。
对应的计算公式为:V (G) = P + 1
其中 P 为判定节点数,常见的判定节点有:
- if 语句
- while 语句
- for 语句
- case 语句
- catch 语句
- and 和 or 布尔操作
- ? : 三元运算符
对于多分支的 case 结构或 if - else if - else 结构,统计判定节点的个数时需要特别注意:必须统计全部实际的判定节点数,也即每个 else if 语句,以及每个 case 语句,都应该算为一个判定节点。
降低圈复杂度的方法
常用的方法有:
- 简化、合并条件表达式
- 将条件判定提炼出独立函数
- 将大函数拆成小函数
- 以明确函数取代参数
- 替换算法
重构思路:圈复杂度高多层嵌套:卫语句,降低if嵌套层数
目前我们业务常用的重构优化,就是卫语句
卫语句(guard clauses)是一种改善嵌套代码的优化代码。将经过多级嵌套的代码使用卫语句优化之后,代码嵌套层数可以降低,因此改使用卫语句能降低代码的复杂程度。卫语句是通过对原条件进行逻辑分析,将某些要害(guard)条件优先作判断,从而简化程序的流程走向,因此称为卫语句。
有些嵌套判断语句,它们都是在 if 里面的条件是真的情况才执行,也就是说它们都是走的正常情况,才会导致这么无限嵌套下去,那么我们从反面思考是不是就可以终止这种情况呢?也就是我们把不正常的条件先摘出来处理,剩下的就都是正常情况了。这其实就是卫语句的思考模式,也就是逆向思考。卫语句可以减少 if-else 语句嵌套的情况出现
如求 100 以内同时是 3、4、5 的倍数的题,如果我们根据题目所说的老老实实地判断符合倍数的情况,将会写成这样(假设每个 if 语句只判断一个条件):
for (int i = 1; i <= 100; i++) {
if (i%3 == 0){
if (i%4 == 0){
if (i%5 == 0){
System.out.println(i);
}
}
}
}
这就是 “横放着的金字塔”,而如果我们逆向思考,从是 3、4、5 的倍数的反面思考,也就是哪些情况不是 3、4、5 的倍数,先把这些情况摘出来,然后结束本次循环,继续找下一个数。这样我们就能防止多层嵌套了:
for (int i = 1; i <= 100; i++) {
if (i%3 != 0){
continue;
}
if (i%4 != 0){
continue;
}
if (i%5 != 0){
continue;
}
System.out.println(i);
}
只有在上面三个条件都不成立的情况下,才会走到最后一步输出的语句。也就是排除那些不符合条件的情况,剩下的自然就是符合条件的了。希望通过这个小例子能让你明白到底什么是卫语句。
重构思路:针对复杂度高的代码块抽象为方法
选中好需要重构的代码块内容,然后右键 refactor---> extract method
idea会自动帮你把代码块抽象出一个公共方法,与此同时,如果文件上下文内有相似代码块,还会提示你是否一起重构, 我们也点击是,这样还可以把重复的代码块都一起抽象出来,直接调用方法,这样复杂度就一定程度会降低