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

背景

常听开发同学吐槽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> ...
相关推荐
重庆小透明5 分钟前
力扣刷题记录【1】146.LRU缓存
java·后端·学习·算法·leetcode·缓存
博观而约取30 分钟前
Django 数据迁移全解析:makemigrations & migrate 常见错误与解决方案
后端·python·django
寻月隐君1 小时前
Rust 异步编程实践:从 Tokio 基础到阻塞任务处理模式
后端·rust·github
GO兔1 小时前
开篇:GORM入门——Go语言的ORM王者
开发语言·后端·golang·go
Sincerelyplz1 小时前
【Temproal】快速了解Temproal的核心概念以及使用
笔记·后端·开源
爱上语文1 小时前
Redis基础(6):SpringDataRedis
数据库·redis·后端
Lemon程序馆1 小时前
速通 GO 垃圾回收机制
后端·go
Aurora_NeAr2 小时前
Spark SQL架构及高级用法
大数据·后端·spark
杰尼橙子2 小时前
DPDK BPF:将eBPF虚拟机的灵活性带入到了DPDK的高性能用户态
后端·性能优化
代码老y2 小时前
Spring Boot + 本地部署大模型实现:优化与性能提升
java·spring boot·后端