关于圈/环复杂度
关于圈/环复杂度
圈/环复杂度(Cyclomatic complexity)是一种代码复杂度的衡量标准。其由托马斯·J·麦凯布(Thomas J. McCabe, Sr.)于1976年提出,用来表示程序的复杂度。它可以用来衡量一个模块判定结构的复杂程度,也可以理解为覆盖所有可能情况所需的最少测试用例数。圈/环复杂度大说明程序代码的判断逻辑复杂,可维护性不好。程序的可能错误和高的圈复杂度有着很大关系。
圈/环复杂度的计算方法:
- 点边计算法:V(G)=E-N+2
- 节点判定法:V(G)=P+1
E:控制流图中边的数量 N:控制流图中节点的数量 P:判定节点数量
控制流图:
控制流图是程序流程图的一种简化,它可以更加突出的表示程序控制流的结构。控制流图只有节点和边两种简单的图示符号。
-
节点:表示基本的程序块,可以是一个单独的语句,也可以是多个顺序执行的语句构成的语句块。
-
判定节点:是节点的一种,是带有分支的节点,典型例子为if语句对应的节点。当判定条件为复合条件时,需要将符合条件分解为简单条件,构成多个判定节点。比如会被分解为
判定节点具体包括:if/while/for/case/catch语句、bool操作、?:三元运算符等。
-
边:连接相关的两个节点
圈/环复杂度与可维护性
圈/环复杂度更高的模块和方法,其缺陷个数可能会更多,会导致维护成本上升,可维护性下降。
圈/环复杂度 | 代码状况 | 可测性 | 维护成本 |
---|---|---|---|
1~10 | 清晰 结构化 | 高 | 低 |
10~20 | 复杂 | 中 | 中 |
20~30 | 非常复杂 | 低 | 高 |
>30 | 不可读 | 不可测 | 非常高 |
利用圈/环复杂度
- 在编写代码时,阶段性检查圈/环复杂度。如果圈/环复杂度并不理想,要及时进行优化,降低圈/环复杂度。这样一方面可以提高代码的可维护性,便于后期代码编写、使用和维护。另一方面可以“在缺陷成为缺陷之前捕获它们”,减少潜在问题,及时止损。
- 圈/环复杂度可以为设计测试用例提供一个很好的参考。好的经验是:创建数量与被测代码圈复杂度值相等的测试用例,以此提升用例对代码的分支覆盖率。
- 在维护或扩展时,为提升代码质量,可以使用圈/环复杂度作为切入点。结合控制流图的直观展示,可以更明确优化位置。
降低圈/环复杂度的方法
- 提炼函数:一段代码可以被组织在一起并独立出来,放进一个独立函数中。尤其是针对常被复用的代码片段。
- 替换算法:把某个算法替换为另一个更清晰的算法。设计算法时应针对问题明确思路。在Java可以充分利用丰富的类库简化算法的某些步骤。
- 简化条件表达式:
- 逆向表达:先写出基本的判断语句,如果条件表达式比较复杂,考虑条件表达式取反是否简单,如果条件表达式取反易于表达,则可以调换表达顺序。
- 分解条件:提炼出独立的函数,以函数名明确判断意图。
- 合并条件:一系列条件判断结果相同时,合并这些条件表达式到一个独立函数中。
- 移除控制标记:减少使用bool类型作为逻辑控制标记,以break/continue/return等代替。
- 以多态取代条件式:针对switch-case,将整个条件式的每个分支放进一个子类的重载方法中,然后将原始函数声明为抽象方法。
- 简化函数调用
- 分离函数:将查询对象值与修改对象值的功能分离到两个不同的函数中。
- 参数化方法:如果多个函数/语句块执行相似的工作只是具体计算的值不同,则将计算的值参数化后建立单一函数执行对参数的计算。
- 以明确函数取代参数:当函数实现完全取决于参数值而采取不同反应时,针对该参数的每一个可能值,建立一个独立函数。人为设置的函数名让代码更容易被理解,而尝试通过参数名结合条件表达式理解函数会比较困难。
注意
- 高圈/环复杂度的代码不一定代表可维护性差。switch-case多重分支是一个典型例子,圈/环复杂度高但是逻辑比较明确。圈/环复杂度于可读性和可维护性的关系要辩证看待。
- 圈/环复杂度相同的代码其他属性不一定相同,可读性、可维护性也可能存在差异。这取决于代码的具体实现方式。
关于圈/环复杂度的感受
参考已有的blogs写完上面的部分后,发现通过圈/环复杂度评估代码是编写代码时的一种很好的方法。尤其是在写降低圈/环复杂度的方法部分时,其中的一些方法和对应的情况在之前练习编写代码时遇到过很多次,比如简化条件表达式和提炼函数等。使用这些方法确实能够很大程度上增强代码的可读性和可维护性。
结合圈/环复杂度和降低圈/环复杂度的方法重新审查了Lab1和Lab2中编写的代码。发现两次实验给出的TurtleSoup、Graph等框架很好地体现了简化函数调用中分离函数和参数化等思想,使得框架中每个方法在编写时更加容易,编写出的代码圈/环复杂度比较低。在Lab2中GraphPoet和FriendShipGraph类中的实现的一些方法圈/环复杂度较高,虽然仍处在<10范围内,但是可读性并不好,当时编写时也感到维护比较费劲。后续编写代码时应在编写过程中更多关注圈/环复杂度等和可维护性相关的判断指标,编写过程中阶段性及时进行优化。
参考
http://kaelzhang81.github.io/2017/06/18/详解圈复杂度/#圈复杂度和软件质量
https://blog.csdn.net/luoxuexi2020/article/details/117968559