软件度量-圈复杂度
最近在做ESLint规则配置,重新注意了下
complexity
规则,之前对该规则不够重视,理解有盲区,于是系统学习,这里mark下。
概念
循环复杂度 Cyclomatic complexity
也称为条件复杂度
或圈复杂度
,是一种软件度量,是由老托马斯·J·麦凯布在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中默认值是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
<module name = "Checker">
<property name="charset" value="UTF-8"/>
<module name="TreeWalker">
<module name="CyclomaticComplexity">
<property name="max" value="10"/>
</module>
</module>
</module>
重构手法
面对圈值过高的函数,我们要做的是重构来解决,具体如何处理呢。
- 提炼函数,做到
函数职责单一
,降低单个函数的复杂度,圈值自然降低 - switch,if else使用的多时,可以考虑多态,考虑map映射来解决多分支问题
- 简化逻辑判断,不断的逻辑合并收拢,有时会发现重复。
ESLint中圈复杂度统计源码分析
这里通过ESLint中相关rule了解下实现原理
eslint/lib.rules/complexity.js
,地址戳这里
1 |
|
计算办法就是,根据AST树,找到这些判定节点,进行对应的复杂度计算即可。如上,switch/case中default不算。
写在最后
- 记得以前看书,印象深的一句话,代码易读性是为了人,面对运行代码的机器,怎么写理论上都行,但问题是人要读和看,那么易读性,可维护性就显得尤为重要。so,重视圈复杂度,这是个很好的指标来督促我们写出易读的代码
- 圈复杂度的意义是
在缺陷成为缺陷之前捕获它们。