在我们开发过程中,是否会有如下疑问.
- 当我们在编写代码的时候,都会想去降低代码复杂度,那么代码复杂度有没有什么硬性的可量化参考指标呢?
- 在遇到屎山代码的时候,我们往往想着去重构代码,那么在你重构之后能不能有一个指标可以佐证重构降低了代码复杂度呢?
- 当看到实习生在编写业务逻辑时没有抽象的思考,代码无脑的使用if-else,导致代码很“脏”,想对整体的代码质量做全面的掌控,有没有办法可以检测这种“脏”代码呢?
对于上面的问题,我找到了一个概念--圈复杂度
圈复杂度(Cyclomatic complexity)是一种代码复杂度的衡量标准,在1976年由Thomas J. McCabe, Sr. 提出。 在软件测试的概念里,圈复杂度用来衡量一个模块判定结构的复杂程度,数量上表现为线性无关的路径条数,即合理的预防错误所需测试的最少路径条数。圈复杂度大说明程序代码可能质量低且难于测试和维护,根据经验,程序的可能错误和高的圈复杂度有着很大关系。
由上文我们可以知道圈复杂度作为一项重要的指标,对于衡量项目代码质量起着至关重要的作用,那么怎么计算圈复杂度呢?
如果一段代码中不包含控制流语句(条件或判定节点),那么这段代码中只会有一条路径,因此一段代码的初始圈复杂度为1.
那么什么是判定判定节点呢?
if else
、switch case
、for循环
、三元运算符
,等等,都属于一个判定节点
每当出现一个判定节点,那么复杂度就会+1 那么对于一个模块(函数)的圈复杂度而言,基本的标准如下
圈复杂度 | 代码状况 | 可测性 | 维护成本 |
---|---|---|---|
1 - 10 | 清晰、结构化 | 高 | 低 |
10 - 20 | 复杂 | 中 | 中 |
20 - 30 | 非常复杂 | 低 | 高 |
>30 | 不可读 | 不可测 | 非常高 |
function complexityFun (paramA, paramB) {
let result = 1
if (paramA && paramB) {
result--
}
if (paramA || paramB) {
result++
}
for (let i = 0; i < 5; i++) {
result += Math.random()
}
switch (result) {
case 1:
result += 1
break;
case 2:
result += 2
break
default:
result += 3
break
}
return result > 2 ? result : result
}
需要注意的是 ||
和 &&
语句也会被算作一个判定节点,上面的代码中一共有2
个if
语句,一个&&
,一个||
,一个for
循环,两个case
语句,一个三元运算符
,所以代码复杂度为 2+1+1+1+2+1+(初始复杂度 1 )=9
。
不知道你们有没有注意到,某份代码中if-else之类的判断越多,那么那份代码往往就越难维护?
-
一个圈复杂度高的模块(函数),由上文中描述的计算方法来看,必然会有更多的运行分支,要对这样的模块进行如单元测试用例的编写,将会十分复杂,并且后期用例维护也是一个问题。
-
我们总是期望代码是高内聚,低耦合的。一个圈复杂度高的模块就意味着有许多的判定节点,那就意味着这个模块多半没有达到单一职责,也意味着内聚较低.
-
代码可读性是大型项目与团队协作间必须要考虑的一个因素。圈复杂度高的模块(函数),随着逻辑复杂度的增加,代码可读性不可避免的也将降低,不利于成员间相互协作与后期维护,造成不想改,不敢改的局面。
现在我们就用工具把那些代码中的“坏味道”揪出来.
eslint
作为最常用的前端代码检查检查工具,也提供了圈复杂度的检查的功能limit-cyclomatic-complexity
开启 rules 中的 complexity 规则,并将圈复杂度大于 5 的代码设置 warn
.
rules: {
complexity: [
'warn',
{ max: 5 }
]
}
这样 eslint
就会检测出所有函数的代码复杂度,并输出一个类似信息.
Arrow function has a complexity of 6. Maximum allowed is 0
Method 'complexityFun' has a complexity of 9. Maximum allowed is 0
...
这款vscode插件可以直接看到当前函数的圈复杂度,并且按照等级标注颜色 插件链接
以上的工具都只是简单的展示,如果想更多的功能,例如
- 可视化图表统计
- 圈复杂度变动的趋势等功能
- 代码分析报告
那么需要更强力的工具,因此在这里推荐一个我的一个项目,欢迎大家多多start,github地址:https://github.com/caozhong1996/jsplato
可以通过安装npm包使用
-
npm install --save-dev jsplato
-
package.json添加
"scripts" : { "jsplato": "jsplato -r -d ./report ./src", }
-
npm run jsplato
本项目基于
es6-plato
,但是es6-plato
不支持扫描.vue文件,因此基于它修改得以支持.vue文件的扫描
圈复杂度的比值如果在上涨,说明代码相比之前变得难以理解。这不仅使会面临意外的功能缺陷和风险,也会造成相关功能的模块中有过多认知负担,变得难以修改和测试代码
- 介绍了圈复杂度的相关知识
- 推荐了相关的检查工具及其使用
至于如何降低圈复杂度就是仁者见仁智者见智了,相关的文章也非常多,无非就是使用策略模式,封装,分治之类的做法,此处便不再赘述了
最后,感谢大家的阅读,如有错误,请各位不吝赐教.