定义

循环复杂度(Cyclomatic complexity)也称为条件复杂度或圈复杂度,是一种软件度量,是由老托马斯·J·麦凯布在1976年提出,用来表示程序的复杂度,其符号为VG或是M。循环复杂度由程序的源代码中量测线性独立路径的个数。

循环复杂度是由程序的控制流图来计算:有向图的节点对应程序中个别的代码,而若一个程序运行后会立刻运行另一代码,会有边链接二代码对应的节点。圈复杂度可应用在程序的子程序、模块、方法或类别。

“循环复杂度”的名称有时会让人误解,因为此复杂度不只计算程序中的循环(循环)个数。循环复杂度是指程序的控制流图中,若将结束点到启始点再增加一个边时,控制流图中的圈(几个边形成封闭路径)的个数

原理

公式一

一段程序的循环复杂度是其线性独立路径的数量。若程序中没有像IF指令或FOR循环的控制流程,因为程序中只有一个路径,其循环复杂度为1,若程序中有一个IF指令,会有二个不同路径,分别对应IF条件成立及不成立的情形,因此循环复杂度为2。

数学上,一个结构化程序的循环复杂度是利用程序的控制流图来定义,控制流图是一个有向图,图中的节点为程序的基础模块,若一个模块结束后,可能会运行另一个模块,则用箭头链接二个模块,并标示可能的运行顺序。循环复杂度M可以用下式定义: M = E − N + 2P

其中: E 为图中边的个数, N 为图中节点的个数, P 为连接组件的个数。

for

简单程序的控制流图。此程序由红色的节点开始运行,然后进入循环(红色节点下由三个节点组成),离开循环后有条件分支,最后运行蓝色节点后结束,此控制流图中,E = 9, N = 8, P = 1,因此其循环复杂度为 9 - 8 + (2*1) = 3。

公式二

另一个计算循环复杂度的公式,需修改控制流图,每一个结束点都增加一个到启始点的边。修改后的图称为强连通,任何二个节点A和B,都可以找到从A到B及从B到A的路径。程序的循环复杂度等于此图中回路的个数(也称为第一贝蒂数),其公式如下: M = E − N + P

上式可以视为计算图中线性独立回路(回路内不包括其他回路)的个数,由于控制流图增加结束点到启始点的边,因此对应一个结束点至少会有一个回路。

对于单一的程序(或副程序或方法),P恒为1。但循环复杂度可以适用于同时分析许多程序或副程序的情形(例如针对一个类别中的所有方法),此时P等于程序的个数,因为每一个程序的图都是一个独立的连接组件。若每一个程序都只一个结束点,P也可以视为是结束点的个数。

可以证明任何只有一个进入点及结束点的结构化程序,其循环复杂度等于程序中决策点(if指令及条件循环)个数加1

循环复杂度也可以延伸到多个结束点的程序,此时的循环复杂度如下: π - s + 2

其中: π是程序中决策点的个数, s为结束点的个数。

for

对应同一个程序的控制流图,但多加一个从结束点到启始点的边,因此为强连通的控制流图,若利用此图计算循环复杂度,其公式为M=E-N+P,而E = 10、N = 8、P = 1,因此循环复杂度为3

应用

限制软件复杂度

程序设计者需计算其开发模块的复杂度,若一模块的循环复杂度超过10,需再分区为更小的模块。NIST(国家标准技术研究所)的结构化测试方法论已此作法略作调整,在一些特定情形下,模块循环复杂度上限放宽到15会比较合适。

模块内聚性的评估

可以预期一个复杂度较高模块的内聚性会比较低,至少不会到功能内聚性的程度。一个有高复杂度及低内聚性的模块中会有许多的决策点,这类的模块多半运行超过一个明确定义的任务,因此内聚性较低。一个2005年的研究发现复杂度的度量和由专家评估的模块内聚性有高度负相关,反而针对内聚性设计的度量和专家评估结果之间的相关性还比较不明显。

推测软件缺陷个数

许多这类研究发现循环复杂度和缺陷个数有高度的正相关:循环复杂度最高的模块及方法,其中的缺陷个数也最多。

莱斯·哈顿认为利用循环复杂度来预测缺陷个数,和利用源代码行数来预测缺陷个数的结果大致相近

原文链接

学习方法

不要担心以后会做什么决策,先好好深入学习。 将新技能当作玩具一般快乐地练习。 以小承诺的方式找时间来写代码,就像你一开始安慰自己只逛一小会时间网站。 慢下来,步子迈的越小,学得越快。