介绍
圈复杂度 (Cyclomatic complexity) 是一种代码复杂度的衡量标准,也称为条件复杂度或循环复杂度,它可以用来衡量一个模块判定结构的复杂程度,数量上表现为独立现行路径条数,也可理解为覆盖所有的可能情况最少使用的测试用例数。简称 CC 。其符号为 VG 或是 M 。
圈复杂度 在 1976 年由 Thomas J. McCabe, Sr. 提出。
简单的说圈复杂度可以表明你的代码逻辑复杂程度和代码质量,会导致代码难以维护,易出错。
衡量标准
圈复杂度 | 代码状况 | 建议 |
---|---|---|
0 - 5 | 清晰 | 无需重构 |
5 - 10 | 略显复杂 | 建议重构 |
> 10 | 比较复杂 | 强烈建议重构 |
以下更合理:(看项目代码性质决定)
- V(G) ∈ [ 0 , 10 ]:代码质量不错;
- V(G) ∈ [ 11 , 15]:可能存在需要拆分的代码,应当尽可能想措施重构;
- V(G) ∈ [ 16 , ∞ ):必须进行重构;
计算方法
1. 点边计算法
M = E − N + 2P
- E: 表示控制流程图中边的数量
- N:表示控制流程图中节点的数量
- P:独立组件的数目
流程控制图
控制流程图,是一个过程或程序的抽象表现,是用在编译器中的一个抽象数据结构,由编译器在内部维护,代表了一个程序执行过程中会遍历到的所有路径。它用图的形式表示一个过程内所有基本块执行的可能流向, 也能反映一个过程的实时执行过程。
2.节点判定法
有一个简单的计算方法,就是通过计算所有的判定节点的数量再加上1。判定节点有if else
, switch case
, for
循环,|| 和 &&
, 三元运算等等。
例如:
function foo (i) {
let r = null
if (i == 'a'’) {
r = 1
} else if (i == 'b') {
r = 2
} else {
r = 3
}
switch (r) {
case 1:
return 'do something with a'
case 2:
return 'do something with b'
case 3:
return 'do something with c'
default:
return r ? 'do nothing' : 'do something you like'
}
}
上面的代码有2个if
, 一个switch case
判断,包括了3个case
,这里要注意下,整个switch case
的绩点按照case
数量来算,还有一个三元运算,所以圈复杂度是2+3+1+1=7
.
如何优化代码
理论来说最简单的点就是在独立组建中(前端可以理解在一个function
)少一点if else
, switch case
, for循环
,&& 和 ||
以及三元运算符等等。
那么优化代码可以分为两大类:
1.fucntion优化
-
提炼函数-单一指责
单一职责原则:每个类都应该有一个单一的功能,一个类应该只有一个发生变化的原因。
上面代码可以优化如下:function foo (i) { let r = null if (i == 'a'’) { r = 1 } else if (i == 'b') { r = 2 } else { r = 3 } doThings(r) } function doThings (r) { switch (r) { case 1: return 'do something with a' case 2: return 'do something with b' case 3: return 'do something with c' default: return r ? 'do nothing' : 'do something you like' } }
2.判断节点优化
简单点就是利用一切方法消灭判断节点,可以利用对象,数组等其他任何合理方式。
-
抽象成对象配置
对于一些判断执行类的业务可以抽象成对象的形势,合理利用对象键值对避免使用冗余的判断节点。
对于上面的代码可以如下优化:function foo (i) { let r = null if (i == 'a'’) { r = 1 } else if (i == 'b') { r = 2 } else { r = 3 } const obj = { 0: r ? 'do nothing' : 'do something you like', 1: 'do something with a', 2: 'do something with b', 3: 'do something with c' } return obj[r] }
看上面的例子将判断缩减3,圈复杂度就等于2+1+1=4,其实我们的代码还有很多带优化的地方,需要大家自己去研究,这里就不一一举例了。
如何查看代码的圈复杂的
1.eslint规则
eslint
提供了检测代码圈复杂度的rules
:
我们开启eslint
的rules
的complexity
规则,max
表示允许最大圈复杂度,eslint
就会报警代码的圈复杂度
rules: {
complexity: [
'warn', { max:0 }
]
}
但是这样只能一个一个文件的看,看不到项目代码整体的数据,刚好eslint
有个CLIEngine
(文档),它类似提供一个cli
功能,下面我们介绍下如何使用:
const eslint = require('eslint')
const { CLIEngine } = eslint;
const cli = new CLIEngine({
parserOptions: {
ecmaVersion: 2018,
sourceType: "module", // 允许script引入的js
ecamFeatures: {
jsx: true // 允许扫描jsx文件
}
},
rules: {
complexity: [
'error',
{ max: 1 }
]
},
useEslintrc: true // 使用 eslintrc的配置
});
注: 对于vue文件我们需要配置vue-eslint-parser
先解析vue文件
配置.eslintrc.js
module.exports = {
extends: "eslint:recommended",
parser: "vue-eslint-parser",
}
配置完成后,使用executeOnFiles
对指定文件扫描,获得结果并过滤返回,
const reports = cli.executeOnFiles(['.']).results;
for (let i = 0; i < reports.length; i++) {
const { messages } = reports[i];
for (let j = 0; j < messages.length; j++) {
const { message, ruleId } = messages[j];
if (ruleId === 'complexity') {
console.log(message);
}
}
}
这样我们就能拿到所有我想要的文件的圈复杂度数据了,结果展示,results
:
results: [
{
filePath: "./myfile.js", // 提取
messages: [
{
ruleId: 'complexity', // 只条件
severity: 2,
message:
'Function \'fmtNumber\' has a complexity of 2. Maximum allowed is 1.', // 提取
line: 8, // 提取
column: 1, // 提取
nodeType: 'FunctionDeclaration',
messageId: 'complex',
endLine: 15,
endColumn: 2
}
],
errorCount: 1,
warningCount: 0
}
],
这样对有用的数据提取并汇总,就能统计项目的整体复杂度了
结果展示
小结
希望看完本文能最你有以下帮助:
- 理解圈复杂的计算和意义
- 提升项目代码质量,注重圈复杂度使用
下面是圈复杂度统计的统计工具
cli工具——concc
代码圈复杂度检测(check-cycle-complexity
)源码:github
欢迎使用。
详细使用移步github