代码的圈复杂度和认知复杂度

背景

常听开发同学吐槽xx代码复杂度高,难以理解。"难以理解"是一种主观感受,为了解决这个问题,我们需要将主观感受量化成客观指标。因此我们引入圈复杂度认知复杂度两个概念,帮助我们量化代码复杂度。

概念

圈复杂度

圈复杂度,是一种代码复杂度的衡量标准。它可以用来衡量一个模块判定结构的复杂程度,数量上表现为独立路径条数 ,也可理解为覆盖所有的可能情况最少使用的 测试用例 。圈复杂度大说明程序代码的判断逻辑复杂,质量低且难于测试和维护。

计算方法

方法一

V(G) = E - N + 2

Where

E = the number of edges of the graph.

N = the number of nodes of the graph.

下面是一些典型的控制流程,如if-else,While,until和正常的流程顺序。

Below are some typical control flows, such as if-else, while, until, and normal sequential flows.

方法二 | Method Two

V(G) = P + 1

Where

P = the number of edges of the decision node.

圈复杂度等于控制流中判定节点的数量加一。常见的判定节点包括:

Cyclomatic complexity is equal to the number of decision nodes in the control flow plus one. Common decision nodes include:

  1. if
  2. for
  3. switch case
  4. select case
  5. and / or

一个例子 | Example

css 复制代码
A = 10
   IF B > C THEN
      A = B
   ELSE
      A = C
   ENDIF
Print A
Print B
Print C

以上代码的控制流为:

The control flow of the above code is:

用方法一计算 :V(G) = 7 - 7 + 2 = 2

用方法二计算:V(G) = 1 + 1 = 2

圈复杂度的缺陷

圈复杂度只评估了代码的结构复杂性,对代码可读性的衡量不够准确。以下两段代码圈复杂度都是6,但是代码块1的可读性和可维护性明显比代码块2更好。

go 复制代码
// code snippet 1
// Cyclomatic Complexity = 6
func getWeight(i int) string { // +1
    switch {
    case i <= 0: // +1
        return "no weight"
    case i < 10: // +1
        return "light"
    case i < 20: // +1
        return "medium"
    case i < 30: // +1
        return "heavy"
    case i < 40: // +1
        return "very heavy"
    default:
        return "super heavy"
    }
}

// code snippet 2 // Cyclomatic Complexity = 6 
func sumOfNonPrimes(limit int) int { // +1 
    var bAdd bool = false 
    var sum int = 0 
    for i := 0; i < limit; i++ { // +1 
        if i <= 2 { // +1 
            continue 
        } 
        for j := 2; j < i; j++ { // +1 
            if i%j == 0 { // +1 
                bAdd = false break 
            }
            bAdd = true
        } 
        if bAdd { // +1 
            sum += i 
        } else { 
            sum -= 1 
        }
    } 
    return sum
}

认知复杂度 | Cognitive Complexity

en.wikipedia.org/wiki/Cognit...

www.sonarsource.com/docs/Cognit...

为了弥补圈复杂度的不足,我们引入认知复杂度概念。与圈复杂度主要考虑代码的结构复杂性不同,认知复杂度更关注代码的可读性、嵌套程度以及理解代码逻辑时的认知负担。

计算方法 | How to Caculate

认知复杂度的计算基于以下规则:

  • 线性代码:不会增加认知复杂度。
  • 分支结构(如 if, else if, switch,else):增加1的认知复杂度。
  • 循环结构(如 while, for):增加1的认知复杂度。
  • 嵌套结构:每增加一层嵌套,复杂度额外增加1。
  • 逻辑运算符:连续&&||不额外增加认知复杂度

注:不同统计工具的规则略有不同

两者主要区别 | Differences Between the Two Types of Complexity

  • 圈复杂度对每一个switch case加1分,但是认知复杂度将switch整体视为1分
  • 认知复杂度增加了对嵌套深度的计分,嵌套结构越深,得分越高
  • 认知复杂度增加了对else的计分
  • 圈复杂度对每一个&&||计分,认知复杂度仅对一连串不同的逻辑运算符计分
  • 回顾以下两个代码块,两者圈复杂度都是6,但是认知复杂度分别为1和10,认知复杂度得分更好得衡量了代码的可读性
go 复制代码
// code snippet 1
// Cognitive Complexity = 1
func getWeight(i int) string {
    switch { // +1
    case i <= 0: 
        return "no weight"
    case i < 10:
        return "light"
    case i < 20:
        return "medium"
    case i < 30: 
        return "heavy"
    case i < 40:
        return "very heavy"
    default:
        return "super heavy"
    }
}

// code snippet 2 
// Cognitive Complexity = 10
func sumOfNonPrimes(limit int) int { 
    var bAdd bool = false 
    var sum int = 0 
    for i := 0; i < limit; i++ { // +1 
        if i <= 2 { // +2
            continue 
        } 
        for j := 2; j < i; j++ { // +2 
            if i%j == 0 { // +3 
                bAdd = false break 
            }
            bAdd = true
        } 
        if bAdd { // +2 
            sum += i 
        } else { // +1
            sum -= 1 
        }
    } 
    return sum
}

如何使用指标 | How to Use this Metrics

圈复杂度和认知复杂度是对代码质量的一种简单直观的衡量。圈复杂度低的代码不一定是好代码,但是圈复杂度高的代码必定是质量低的。因此,为代码仓库的圈复杂度和认知复杂度划定上限,是提升代码质量的有效方法。

本地统计工具

以go语言为例,可以用

  • Cyclomatic complexity

    • gocyclo
  • Cognitive complexity

    • gocognit
bash 复制代码
# install
go install github.com/fzipp/gocyclo/cmd/gocyclo@latest
go install github.com/uudashr/gocognit/cmd/gocognit@latest

# Usage:
    gocyclo [flags] <Go file or directory> ...
    gocognit [flags] <Go file or directory> ...
相关推荐
等什么君!8 分钟前
springmvc-拦截器
后端·spring
brzhang31 分钟前
代码即图表:dbdiagram.io让数据库建模变得简单高效
前端·后端·架构
Jamesvalley36 分钟前
【Django】新增字段后兼容旧接口 This field is required
后端·python·django
秋野酱1 小时前
基于 Spring Boot 的银行柜台管理系统设计与实现(源码+文档+部署讲解)
java·spring boot·后端
獨枭1 小时前
Spring Boot 连接 Microsoft SQL Server 实现登录验证
spring boot·后端·microsoft
shanzhizi1 小时前
springboot入门-controller层
java·spring boot·后端
电商api接口开发2 小时前
ASP.NET MVC 入门指南三
后端·asp.net·mvc
声声codeGrandMaster2 小时前
django之账号管理功能
数据库·后端·python·django
我的golang之路果然有问题3 小时前
案例速成GO+redis 个人笔记
经验分享·redis·笔记·后端·学习·golang·go
嘻嘻嘻嘻嘻嘻ys3 小时前
《Vue 3.3响应式革新与TypeScript高效开发实战指南》
前端·后端