最近在做ESLint规则配置,重新注意了下complexity规则,之前对该规则不够重视,理解有盲区,于是系统学习,这里犹豫的彩虹下。
概念循环复杂度 Cyclomatic complexity也称为条件复杂度或圈复杂度,是一种软件度量,是由kydgz在1976年提出,用来表示程序的复杂度
圈复杂度计算
公式
M = E ? N + 2P
其中
E 为图中边的个数
N 为图中节点的个数
P 为连接组件的个数[程序的个数,对于单一的程序,P恒为1],如下图是1
如图,E = 9, N = 8, P = 1,因此其循环复杂度为 9 – 8 + (2*1) = 3
以上是标准的公式,但是简单的办法实际上是判定节点数量再加一
节点判定
个人喜欢这样直接算,较为简单
判定节点if语句
conditional语句,比如?:
for语句
while语句
try语句
switch/case语句
注意从1开始,一直往下通过程序
遇到以下关键字,或者其它同类的词,就加1:if,while,repeat,for,and,or
if-else-if,switch-case语句中的每一种情况都加1
圈复杂度参考值
这里贴下圈值与风险值对比表格。可以看出20是个已经在维护性边缘,当然10最佳。
01 to 10 – Minimal Risk, Easy to maintain
11 to 20 – Moderate Risk, Harder to maintain
21 to 50 – High Risk,Candidates for refactoring/redesign
Over 50 – Very High Risk
注意eslint中默认值便宜美国vps是20,checkstype中默认值是10
圈复杂度小不一定意味着代码好,但是太大,代码一定不好,不具备维护性,这点需要明确
检测手段
开发中为了确保代码风格的统一,我们往往通过eslint,checkStyle等工具检测代码,对于不满足的规范报错提示。当前这些检测工具都支持了圈复杂度
eslint-rules
1
2
3{
‘complexity’: [‘error’, { ‘max’: 20 }]
}
checkStyle
1
2
3
4
5
6
7
8
9
10
11<?xml version=”1.0″?>
重构手法
面对圈值过高的函数,我们要做的是重构来解决,具体如何处理呢。
提炼函数,做到函数职责单一,降低单个函数的复杂度,圈值自然降低
switch,if else使用的多时,可以考虑多态,考虑map映射来解决多分支问题
简化逻辑判断,不断的逻辑合并收拢,有时会发现重复。
ESLint中圈复杂度统计源码分析
这里通过ESLint中相关rule了解下实现原理
eslint/lib.rules/complexity.js,地址戳这里
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34/**
* Increase the switch complexity in context
* @param {ASTNode}node node to evaluate
* @returns {void}
* @private
*/
function increaseSwitchComplexity(node){
// Avoiding `default`
if (node.test) {
increaseComplexity();
}
}
return {
FunctionDeclaration: startFunction,
FunctionExpression: startFunction,
ArrowFunctionExpression: startFunction,
“FunctionDeclaration:exit”: endFunction,
“FunctionExpression:exit”: endFunction,
“ArrowFunctionExpression:exit”: endFunction,
CatchClause: increaseComplexity,
ConditionalExpression: increaseComplexity,
LogicalExpression: increaseComplexity,
ForStatement: increaseComplexity,
ForInStatement: increaseComplexity,
ForOfStatement: increaseComplexity,
IfStatement: increaseComplexity,
SwitchCase: increaseSwitchComplexity,
WhileStatement: increaseComplexity,
DoWhileStatement: increaseComplexity
};
计算办法就是,根据AST树,找到这些判定节点,进行对应的复杂度计算即可。如上,switch/case中default不算。
写在最后记得以前看书,印象深的一句话,代码易读性是为了人,面对运行代码的机器,怎么写理论上都行,但问题是人要读和看,那么易读性,可维护性就显得尤为重要。so,重视圈复杂度,这是个很好的指标来督促我们写出易读的代码
圈复杂度的意义是在缺陷成为缺陷之前捕获它们。
参考链接
91793792