详解圈复杂度
圈复杂度概念
圈复杂度(Cyclomatic complexity,简写CC)也称为条件复杂度,是一种代码复杂度的衡量标准。由托马斯·J·麦凯布(Thomas J. McCabe, Sr.)于1976年提出,用来表示程序的复杂度,其符号为VG或是M。它可以用来衡量一个模块判定结构的复杂程度,数量上表现为独立现行路径条数,也可理解为覆盖所有的可能情况最少使用的测试用例数。圈复杂度大说明程序代码的判断逻辑复杂,可能质量低且难于测试和 维护。程序的可能错误和高的圈复杂度有着很大关系。
圈复杂度计算方法
点边计算法
圈复杂度的计算方法很简单,计算公式为:
V(G) = E - N + 2
其中,e表示控制流图中边的数量,n表示控制流图中节点的数量。
几个节点通过边连接。下面是典型的控制流程,如if-else,While,until和正常的流程顺序:
节点判定法
其实,圈复杂度的计算还有更直观的方法,因为圈复杂度所反映的是“判定条件”的数量,所以圈复杂度实际上就是等于判定节点的数量再加上1,也即控制流图的区域数,对应的计算公式为:
V (G) = P + 1
其中P为判定节点数,判定节点举例:
if语句
while语句
for语句
case语句
catch语句
and和or布尔操作
?:三元运算符
对于多分支的CASE结构或IF-ELSEIF-ELSE结构,统计判定节点的个数时需要特别注意一点,要求必须统计全部实际的判定节点数,也即每个ELSEIF语句,以及每个CASE语句,都应该算为一个判定节点。
判定节点在模块的控制流图中很容易被识别出来,所以,针对程序的控制流图计算圈复杂度V(G)时,一般采用点边计算法,也即V(G)=e-n+2;而针对模块的控制流图时,可以直接使用统计判定节点数,这样更为简单。
圈复杂度计算练习
练习1:
void sort(int * A)
{
int i=0;
int n=4;
int j = 0;
while(i < n-1)
{
j = i +1
while(j < n)
{
if (A[i] < A[j])
swap(A[i], A[j]);
}
i = i + 1
}
}
使用点边计算法绘出控制流图:
其圈复杂度为:V(G) = 9 - 7 + 2 = 4
练习2:
U32 find (string match){
for(auto var : list)
{
if(var == match && from != INVALID_U32) return INVALID_U32;
}
//match step1
if(session == getName() && key == getKey())
{
for (auto& kv : Map)
{
if (kv.second == last && match == kv.first)
{
return last;
}
}
}
//match step2
auto var = Map.find(match);
if(var != Map.end()&& (from != var->second)) return var->second;
//match step3
for(auto var: Map)
{
if((var.first, match) && from != var.second)
{
return var.second;
}
}
return INVALID_U32;
};
其圈复杂度为:V(G) = 1(for) + 2(if) + 2(if) + 1(for) + 2(if) + 2(if) + 1(for) + 2(if) + 1= 14
圈复杂度的意义
在缺陷成为缺陷之前捕获它们。
圈复杂度与缺陷
一般来说圈复杂度大于10的方法存在很大的出错风险。圈复杂度和缺陷个数有高度的正相关:圈复杂度最高的模块和方法,其缺陷个数也可能最多。
圈复杂度与结构化测试
此外,它还为测试设计提供很好的参考。一个好的用例设计经验是:创建数量与被测代码圈复杂度值相等的测试用例