代码的圈复杂度

圈复杂度的概念

圈复杂度(Cyclomatic complexity,CC)也称为条件复杂度,是一种衡量代码复杂度的标准,其符号为V(G)。

麦凯布最早提出一种称为“基础路径测试”(Basis Path Testing)的软件测试方式,测试程序中的每一线性独立路径,所需的测试用例个数即为程序的圈复杂度。

圈复杂度可以用来衡量一个模块判定结构的复杂程度,其数量上表现为独立路径的条数,也可理解为覆盖所有的可能情况最少使用的测试用例个数。
圈复杂度可应用在程序的子程序、模块、方法或类别。

圈复杂度与出错风险

程序的可能错误和高的圈复杂度有着很大关系,圈复杂度最高的模块和方法,其缺陷个数也可能最多。
圈复杂度大说明程序代码的判断逻辑复杂,可能质量低,且难于测试和维护。

代码复杂度低,代码不一定好,但代码复杂度高,代码一定不好。

圈复杂度代码状况可测性维护成本
1 - 10清晰,结构化
10 -20复杂
20 - 30非常复杂
>30不可读不可测非常高

一般来说,圈复杂度大于10的方法存在很大的出错风险。

圈复杂度与测试

测试驱动的开发较低圈复杂度值 之间存在着紧密联系。
因为在编写测试用例时,开发人员会首先考虑代码的可测试性,从而倾向编写简单的代码(因为复杂的代码难以测试)。
一个好的测试用例设计经验是:创建数量与被测代码圈复杂度值相等的测试用例,以此提升测试用例对代码的分支覆盖率。

计算方法

通常有两者方式可以计算 V(G),不管用哪种方式计算,结果都是一样的。

点边计算法

V(G) = e - n + 2

其中 e 为控制流程图中边的数量,n 表示控制流图中节点的数量。

分割面积法

V(G) = R

其中 R 表示被控制流程图分割的面积的数量。

控制流程图

image.png

示例

示例一

function a() {
  // codes
}

示例二

function a(x) {
  if (x === 1) {
    // codes
  } else if (x === 2) {
    // codes
  } else {
    // codes
  }
}

示例三

function a(x) {
  for (let i = 0; i < x.length; i++) {
    if(x[i]) {
      // codes
    } else {
      // codes
    }
  }
}

使用 ESLint 限制代码的圈复杂度

只需要配置 ESLint 的 complexity 属性即可,数组中的第一个元素为提示等级,第二个元素为最大圈复杂度的阈值(默认为 20)。

plugins: ["@typescript-eslint", "unused-imports", "rulesdir"],
rules: {
  "no-debugger": 1,
+  complexity: ["error", 10],
  "no-undefined": 1,