背景

代码的可测试性和可维护性是非常重要的,比如,下面的代码:


只是输出简单的"Hello, world", 大家都不会否认这个代码写得太复杂太难维护了。那么,有没有什么度量指标来度量什么样的代码是简单的,可维护的代码呢?

圈复杂度

度量代码复杂度的指标有很多,比如LOC/SLOC/圈复杂度/认知复杂度等等,圈复杂度就是用来度量代码的可维护性,可测试性的,它的使用范围最广。为什么需要圈复杂度呢?
首先,我们介绍一下代码测试覆盖率中的常见的几个概念:

  • 行覆盖率 (Line coverage): 测试覆盖了代码行的比率
  • 函数覆盖率(Function coverage): 测试覆盖到了代码中函数的比例,粒度较大
  • 分支覆盖率(Branches coverage): 测试覆盖到了代码的if/else等执行路径的比例

比较复杂的是分支覆盖率,分支覆盖率指的是测试覆盖跳转分支的比例,程序的跳转分支是是一个非常底层的概念,比如

int foo(int num)
{int a = 1;if (num == 2) {a = 3;}if (num == 3) {a = 5;}if (num == 5) {a = 7;}if (num == 7) {a = 2;}return a;
}

我们可以在这里查看到跳转分支的总数是12个, 如果需要做到将跳转都测试到,需要的测试用例个数是就等同与改函数的圈复杂度,也就是5.

比如下面的例子:

在调用foo(true)时代码运行正常,且有100% code coverage, 但是调用foo(false)时,程序就回crash。

圈复杂度的值实际上对应于如果需要测试到某个函数的所有代码分支,需要的最少的测试用例的个数。上面的例子明显就是测试不充分的表现,代码的圈复杂度是2,我们的测试用例数却是1,flag为false时,这个分支没有测试到。
这里补充说明一下,

再看另外一个例子:

它的控制流程图(CFG: control-flow-graph)如下图1:

图1

2和5处是两个if,我们只需要执行路径如下的3个测试用例:

Case 1: (1, 2, 3, 5, 6, 8)
Case 2: (1, 2, 4, 5, 6, 8)
Case 3: (1, 2, 3, 5, 7, 8)

就可以cover到所有的代码分支。

当然测试了所有的代码分支并不是测试了代码的所有执行路径,因为测试代码所有执行路径基本上是Mission impossible, 比如一个含有25个If/else的函数,要把所有可能的执行路径进行测试,需要的用例个数是2^25=3300万个测试用例。

计算圈复杂度

圈复杂度的计算有两种方法:

  1. 一种是根据程序的CFG图,采用边点法计算,公式如下:

    V(G) = E - N + 2
    Where,
    E = Number of edges
    N = Number of nodes

    如图1中E=9, N = 8, 那么V(G) = 9 - 8 + 2 = 3

  2. 另外一种是计算程序中的if/else if/for/while/&&/||/switch case等的数目:

    V(G) = P + 1

    Where,
    P = Number of decision nodes (node that contains condition)

    如图1中含有两个if, P = 2, 那么V(G) = 2 + 1 = 3

圈复杂度值推荐值

虽然我们可以通过上面的公式计算出来圈复杂度,但是具体采用什么样的圈复杂度值作为代码好坏的评价呢?

  • MISRA在它们的报告的第38页中推荐采用15作为阈值
  • 一些大公司,比如华为等,采用15作为代码合入检查阈值

圈复杂度检测工具

Lizard是一个开源的,支持多语言的圈复杂度检查工具,它具有以下优点:

  • MIT License
  • 维护活跃积极
  • 多语言支持
  • 开箱即用,也可嵌入式使用

降低圈复杂度的方法

降低圈复杂度主要有一下几种方法:

  1. 提取函数,以为圈复杂度主要计算的是单个函数的复杂度,一个函数调用另外一个函数并不会增加这个函数本身的圈复杂度,所以将函数中某个功能单独抽取出去可以降低圈复杂度。
  2. 采用表驱动法减少if分支判断
  3. 避免手写for循环: 下面删除vector中某个元素的例子,可以由最初的圈复杂度4降为2:

认知复杂度

当然,圈复杂度也有它的缺点:圈复杂度将if/for/while等条件语句等同的代入计算圈复杂度值,而不考虑它们的嵌套深度。这样的后果是,对于具有同样圈复杂度的不同函数,我们没法区分这是一个逻辑简单的功能函数还是一个逻辑复杂的功能函数,比如下面两个函数,圈复杂度都是5,但是显然右边的逻辑更复杂,更难维护一些:

基于这个原因,SonarQube引入了认知复杂度(Cognitive Complexity)的概念。可以说,认知复杂度是一种考虑了嵌套深度的,更严格的圈复杂度。下图中对于同一个函数,其圈复杂度是5,认知复杂度是8。

目前SonarQube上同时支持认知复杂度和圈复杂度的检查,当然,如果降低了圈复杂度,一定也同时降低了认知复杂度。


Reference:

  1. http://www.literateprogramming.com/mccabe.pdf
  2. https://archive.org/download/misradevelopmentguidelines/misra_report5_sw_metrics.pdf

圈复杂度 Cyclomatic complexity 介绍相关推荐

  1. 圈复杂度Cyclomatic complexity

    一.什么是圈复杂度 圈复杂度(Cyclomatic complexity,简写 CC)也称为条件复杂度,是模块结构复杂度的度量,数量上表现为独立路径的条数,即合理的预防错误所需测试的最少路径条数. 成 ...

  2. 优化圈复杂度(Cyclomatic complexity, CC)

    圈复杂度(Cyclomatic complexity, CC) 简介 圈复杂度(Cyclomatic complexity,简写CC)也称为条件复杂度,是一种代码复杂度的衡量标准.在很多公司内上线代码 ...

  3. #圈复杂度 - Cyclomatic Complexity

    ##概念 循环复杂度(Cyclomatic complexity)也称为条件复杂度,是一种软件度量,是由老托马斯·J·麦凯布(英语:Thomas J. McCabe, Sr.) 在1976年提出,用来 ...

  4. 圈复杂度 (Cyclomatic Complexity)

    概念 圈复杂度也称条件复杂度,是一种衡量代码复杂度的标准.它可以用来衡量一个模块判定结构的复杂程度,数量上表现为独立现行路径条数,也可以理解为覆盖所有情况最少使用的测试用例数.圈复杂度大说明程序代码的 ...

  5. 如何计算并测量ABAP及Java代码的环复杂度Cyclomatic complexity

    代码的环复杂度(Cyclomatic complexity,有的地方又翻译成圈复杂度)是一种代码复杂度的衡量标准,在1976年由Thomas J. McCabe, Sr. 提出. 在软件测试的概念里, ...

  6. 如何降低前端代码圈复杂度?

    作者 | ConardLi 责编 | maozz 出品 | CSDN(ID:CSDNnews) 写程序时时刻记着,这个将来要维护你写的程序的人是一个有严重暴力倾向,并且知道你住在哪里的精神变态者. 导 ...

  7. 前端代码质量-圈复杂度原理和实践

    写程序时时刻记着,这个将来要维护你写的程序的人是一个有严重暴力倾向,并且知道你住在哪里的精神变态者. 1. 导读 你们是否也有过下面的想法? 重构一个项目还不如新开发一个项目... 这代码是谁写的,我 ...

  8. java中的圈复杂度计算_[代码质量] 圈复杂度和代码质量优化(附带示例代码纠正代码质量)...

    什么是圈复杂度? --------------------------------------- 圈复杂度(Cyclomatic Complexity)是衡量计算机程序复杂程度的一种措施.它根据程序从 ...

  9. 圈复杂度详解以及解决圈复杂度常用的方法

    1.什么是代码圈复杂度? 圈复杂度(Cyclomatic Complexity)是一种代码复杂度的衡量标准,由 Thomas McCabe 于 1976年定义.它可以用来衡量一个模块判定结构的复杂程度 ...

最新文章

  1. 如何轻松实现iOS9多任务管理器效果(iCarousel高级教程)
  2. 用 .NET Memory Profiler 跟踪.net 应用内存使用情况--基本应用篇(转载)
  3. angularjs 元素重复指定次数_leetcode题库-sql练习精讲系列--六、查找重复类问题
  4. 翻译 - EXT JS 5:Controlling an Application with Router
  5. 租号平台正在把“未成年”变成“大人”
  6. 使用nginx负载均衡的webservice wsdl访问不到_谁说前端不用懂,Nginx 反向代理与负载均衡(超实用)...
  7. Ubuntu安装mindoc
  8. android前置摄像头预览,android - 当选择了前置摄像头CameraX预览没有显示任何东西 - 堆栈内存溢出...
  9. 【毕业设计项目】基于单片机的指纹识别系统实现 - 物联网 stm32 c51
  10. DiscuzX3.n系列域名转向问题的解决
  11. LWIP网络开发从入门到精通
  12. 小红书账号分析丨千瓜指数高的小红书账号是否真的优质?
  13. 判断bug属于前端还是后端
  14. CE、FCC、ROSH、CCC认证分别是什么意思?
  15. P2495 [SDOI2011]消耗战(树形dp+虚树)
  16. 《构建高可用Linux服务器 第3版》—— 1.2 全面了解Linux服务器
  17. 用HTML+CSS写一个手机微信界面
  18. 一行代码搭建一个简易的本地文件服务器_附带外网教程_一蓑烟雨任平生
  19. 一、JNI_OnLoad简介
  20. 肖 sir_就业课__014python讲解

热门文章

  1. 一、Maven的概述
  2. Token最核心的价值是建立一套激励机制,INE智联生态一直在践行!
  3. “每一个刹那都是唯一”,意思是说:我们活在今天,就只要做好今天的事就好了,无须担忧明天 或后天的事;我们活在此刻,就要好好珍惜此刻的时光,每一个刹那都是唯一的,不复返的。...
  4. 网络爬虫是什么,我们为什么要学习网络爬虫?
  5. 给大家推荐一个超星图书馆
  6. 《微服务实战》微服务设计原则
  7. 苹果新iPhoto十天销售突破100万份
  8. Android 魅族手机消息不显示(进收纳盒)和始终显示一条推送
  9. Linux drm设置默认分辨率,Wayland如何设置自定义分辨率
  10. 面向对象第二单元总结——魔鬼电梯