圈复杂度是什么?
网上搜圈复杂度,能搜出不少资料来,一定会看到V(G)=e-n+2p这个公式。这个公式简单吗?简单,好理解吗?非常不好理解。其实,我们完全从另外一个角度去理解圈复杂度。
如何保证函数的输出一定是符合预期的?可以通过单元测试用例来保证。如何衡量单元测试用例呢?单元测试有一个指标,就是代码覆盖率,但是代码覆盖率达到100%的时候,就能保证自己的函数是没问题的吗?我们通过一个流程图来演示一下。
函数1的流程图
函数1的流程图很简单,只有一个流程,只要保证经过语句A和语句B处理后的数据符合预期结果,那么这个函数就是正确的。所以只需要写一条单元测试用例,就可以保证这个函数是正确的。这时候单元测试用例的覆盖率是100%。因为每个语句都执行到了。
函数2的流程图
函数2稍微复杂一点,有两个流程。分别是(A,B)和(C,D),只要保证这两个流程的输出符合预期,才能说这个函数是正确的。因此我们需要写两条测试用例。这时候单元测试用例的覆盖率也是100%。
函数3的流程图
函数3的流程图又复杂了一些,首先抛出一个问题,当单元测试用例的覆盖率达到100%的时候,能保证函数是正确的吗?
答案是否。
因为只用写4条测试用例,分别是(A,B), (C,D), (A,E,G), (C,F,H)四条测试用例就能让覆盖率达到100%,但是这4个流程就能覆盖这个函数的所有流程吗?显然,遗漏了(A,E,H)和(C,F,G)这两个流程。
所以,覆盖度达到100%时,并不能保证函数的正确性。这时候,就需要参考函数的圈复杂度了。
一个函数的圈复杂度越高,说明这个函数的子流程越多,因此需要更多的单元测试用例来覆盖。另一方面,圈复杂度越高,代码的可读性和可维护性也就越差。通常,我们需要把圈复杂度控制在15以内。到底要写多少测试用例才能覆盖函数的所有流程呢?这里先不引申了。
通过3个例子的流程图,大家应该有直观体会,如果出现分支语句,就会增加圈复杂度。如果出现分支语句里再包含分支的语句,那就会导致圈复杂度成指数级增长,出现这种情况,要么重新整理代码逻辑,尽可能的不要嵌套使用分支语句;如果本身逻辑就这么复杂,那只能先把内层的分支语句剥离出来,作为一个独立的函数,分别写测试用例。
总结一下,圈复杂度反应了函数的内部的分支数,分支数变多,代码的质量就会降低。