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

背景

常听开发同学吐槽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> ...
相关推荐
uzong15 分钟前
技术故障复盘模版
后端
GetcharZp43 分钟前
基于 Dify + 通义千问的多模态大模型 搭建发票识别 Agent
后端·llm·agent
桦说编程1 小时前
Java 中如何创建不可变类型
java·后端·函数式编程
IT毕设实战小研1 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
wyiyiyi2 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
阿华的代码王国3 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
Jimmy3 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
AntBlack3 小时前
不当韭菜V1.1 :增强能力 ,辅助构建自己的交易规则
后端·python·pyqt
bobz9654 小时前
pip install 已经不再安全
后端
寻月隐君4 小时前
硬核实战:从零到一,用 Rust 和 Axum 构建高性能聊天服务后端
后端·rust·github