关于Go的错误处理争议的持续争论

Go 错误处理之争:持续性讨论与最终反对

golang debate-error-handling(from github.com/MariaLetta/free-gophers-pack)|300

注:本文核心内容由大语言模型生成,辅以人工事实核查与结构调整。

Go语言在错误处理方面的方法一直是编程社区的一贯话题和抱怨,常常被描述为冗长且重复。尽管其普遍存在,但 Go 团队最近宣布暂停追求错误处理语法变化的计划,称缺乏任何提案的共识。

Go 团队立场

Go 团队承认,错误处理的冗长性是关于该语言“最古老和最持久的抱怨”之一。他们表示,他们非常重视社区反馈,并且多年来一直试图解决这个问题。

然而,在尝试了无数次后,包括 Go 团队本身提出的三项全面提案以及“几百”条社区提案,均未能获得“充分(甚至是压倒性)支持”。因此,Go 团队决定不再推进错误处理的语法变化,解释称其提案过程的目标是在较短时间内达成广泛共识,而在此次讨论中并未实现。他们进一步指出,即便是 Google Go 团队最资深的成员也“并不一致地同意下一步应采取的最佳路径”。未来一段时间内,团队将“停止追求错误处理语法的语言变化”,并关闭相关的开源和新提案。

对 Go 错误处理的批评

主要批评集中在 if err != nil 模式的冗长性和重复性上,这些批评者认为,这种模式可以“淹没其他代码”并使函数看起来“50%用于错误处理,而实际上,错误处理做了什么”。许多人认为这是一种“令人不快的大脑死亡部分”,是 Go 编程中最费时的部分。

除此之外,还有以下批评:

  • 错误可以被静默地丢弃或误删,这可能导致程序崩溃。正如一位用户所指出的,虽然 foo, _ := doSomething() 允许忽略错误,但这可能会导致软件不稳定。
  • 函数调用结果无法方便地存储或传递,因为它们不是单一值类型,如 Result 类型。
  • errors.Is 的必要性以及“嵌套”错误与 Go 类型系统的交互被认为是有问题的。
  • 切换在错误上很难。尽管 Go 意在于让开发者定义错误类型以支持 casting 或切换,但一些人观察到,大多数 Go 代码库使用 errors.Newfmt.Errorf,这些不适合基于错误条件进行切换。
  • **标准库中使用 sentinel值 被批评。
  • 与通用类型的交互差异 常常需要使用像 errgroup 这样的包。
  • 返回“虚假值”(当有错误时,值被认为是无效的)是一个有争议的问题。
  • 一些人认为 Go 错误机制基于一种假设,即错误主要用于日志记录,因此开发者需要“绕开路”来开发可以被处理的错误。

一位用户将 Go 的错误处理描述为“Go 最糟糕的部分。所有的一切”。其他人抱怨说,“Go 没有解决错误边界的问题”。

反对变更的论点

尽管存在诸多抱怨,但许多 Go 命名工程师和 Go 团队本身都提出了维持现状的强有力的论点:

  • 有意设计和权衡:Go 的当前错误处理是一种”有意为之且知其权衡”的设计,被视为”满足语言设计理念和编写 Go 代码的开发者中最不令人讨厌的选择”。
  • Go 的成功:Go 是”极为成功”的,”大多数活跃度最高的 Go 程序员对错误处理情况并不在意”。抱怨通常来自于那些不经常使用该语言的人。
  • 偏好显式控制流:许多 Go 用户(包括一名十年级 Go 命名工程师)”强烈偏好 Go 对大多数其他语言的处理方式”,他们认为”隐式控制流是噩梦,最佳避免”。这种显式性质”迫使每个人都要深入思考错误,而不是像其他语言那样”,并确保错误情况得到充分关注。
  • 可读性和清晰度:许多人欣赏”显式、清晰且易于阅读的冗长错误处理”。他们认为,一旦熟悉了 Go,这种冗长性”会融入背景”。
  • 调试优势:显式的 if err != nil 语句使得添加 println 语句或设置断点更容易,因为错误处理逻辑不被新语法隐藏。
  • 语言更改的成本:引入新语言特性需要”非常昂贵”,涉及到更改现有代码、更新文档和调整工具。Go 团队人数较少,并且有”许多其他优先事项”。
  • 用户适应:许多长期 Go 用户报告说,对错误处理风格的抵触情绪”通常会随着他们对 Go 哲学和设计选择的熟悉程度而减弱”。
  • 缺乏共识:反对更改的一个重要论点是,社区无法就任何单一提案达成共识,即使是在 Go 架构师中。一些人认为”不做任何事情是正确的事情”,尤其是因为一个非常流行的 Go 提案是”留 ‘if err != nil’ 不变”。
  • “委员会设计”的担忧:一些人批评 Go 团队的共识驱动方法,称这会导致在”用户可见的更改”上无法采取行动。然而,其他人则进行辩护,他们指出 Go 团队是”委员会中最挑剔的”,避免创建一个”委员会设计垃圾语言”。

提出的解决方案/替代方案(被 Go 团队拒绝)

多年来,为了解决Go语言错误处理的冗长性,提出过众多提案,但均未得到采纳。Go团队探索了多种语法上的解决方案:

  • **check 和 handle 机制(2018年):这是Go团队首个有针对性的尝试,基于一份草案设计,该设计包含了详细的替代方案分析。

  • try 机制:该提议允许以更简洁的方式传播错误,如下面的概念代码示例所示:

    1
    2
    3
    4
    5
    6
    func printSum(a, b string) error {
    x := try(strconv.Atoi(a)) // 如果 strconv.Atoi(a) 返回错误,这个 try() 将从 printSum 中返回
    y := try(strconv.Atoi(b))
    fmt.Println("result:", x + y)
    return nil
    }

    然而,由于它会通过从潜在深层嵌套的表达式中返回,导致“隐藏了控制流的可见性”,因此被认为是“许多人无法接受的”。

  • ? 运算符(2024年):受 Rust语言的 ? 运算符启发,该提议旨在减少冗长代码。概念示例如下:

    1
    2
    3
    4
    5
    6
    func printSum(a, b string) error {
    x := strconv.Atoi(a) ? // 如果 strconv.Atoi(a) 返回错误,这个 ? 将从 printSum 中返回
    y := strconv.Atoi(b) ?
    fmt.Println("result:", x + y)
    return nil
    }

    尽管小范围的非正式用户研究显示,大多数参与者能够正确猜测其含义,但该提议也“迅速被评论和许多细微建议淹没”,最终未能获得广泛支持。

  • 社区驱动的提案还包括:

    • 像 Rust 或 Swift 中的 Result<Value, Failure> 类型的和类型: 被认为是一个更干净的方式来表示成功或失败,与 Rust 或 Swift 类似。这将提供“更好的类型化和枚举错误类型”。
    • with 条款: 提议的一个隐式处理错误的构造,在一个块中.
    • or 关键字想法: 语法糖,通常与错误处理逻辑结合使用,例如:
1
n := strconv.Atoi(s) or |err| { return fmt.Errorf("foo: %w", err) }

Go团队表示,它已经“全面探索了设计空间七年”,但未能找到社区共识。

Go 设计哲学

Go 的错误处理机制深刻反映了其核心设计理念:

  • 最小化语言表面积:Go 目标是保持一个小型语言。如果新增语法构造导致同一事物有多种方法实现,这就违背了这一原则。
  • 显式控制流:Go 的设计优先显式控制流,即你读到的就是你得到的。try 机制被部分拒绝,原因在于它“通过从潜在深度嵌套的表达式中返回,从而隐藏了控制流”。这种显式性被视为“迫使每个人更深入地思考错误”。
  • 实用主义:Go 常被描述为实用主义语言。当前的错误处理虽然不一定优雅,但“有效工作”,并被认为“非常符合 Go 的实用主义精神”。
  • 零值语言:Go 是一个“零值语言”,这意味着“所有值都包含有用的状态”。这与类似 Result 类型的单adic 解决方案(如 Result 类型)产生根本性冲突,因为后者需要“设计一个完全新的语言,具有完全不同的关于如何表示状态的概念”。
  • 抵抗变更:该语言在过去 15 年中有强烈的“抵抗变更倾向”。任何变化都经过仔细考虑,并且通常只有在达成广泛共识之前才会被拒绝。

相关语言设计主题

关于 Go 错误处理的讨论经常与更广泛的语言设计辩论相交:

  • Generics:在错误处理之前,Go 的最大抱怨是缺乏Generics。Generics从 Go 开源发布后花了 13 年才实现。与错误处理不同,“没有人被迫使用Generics”。一些人批评 Go 初始的“错误”静态类型而无参数多态性,认为这可能是错误处理问题的根源。
  • 异常(panic/recover:Go 有类似 Java 的异常处理方式,通过 panicrecover 来推送值。这些通常被视为“不可恢复的错误”。有关新错误处理语法的讨论有时涉及是否会“重新发明异常”。
  • nil 安全性/严格的 null 检查:一些 Go 用户认为,缺乏严格的 null 检查是“在实践中比错误处理更大的问题”,并建议编译器支持消除 nil dereference 引发的 panics。
  • 并发:Go 设计者将并发(CSP 飲法中的 goroutines)置于首位,导致一个“简单的错误处理模型,这不会干扰 goroutines 作为多样化的方式”。
  • 与其他语言的比较:讨论经常提及其他语言如 Rust (Result? 运算符)、C++ (std::expected)、Java(检查型异常,深受憎恨)、Swift、JavaScript、Kotlin 和 Python 的错误处理方法。

更多内容

最近文章:

随机文章:


更多该系列文章,参考medium链接:

https://wesley-wei.medium.com/list/you-should-know-in-golang-e9491363cd9a

English post: https://programmerscareer.com/debate-error-handling/
作者:微信公众号,Medium,LinkedIn,Twitter
发表日期:原文在 2025-06-05 01:35 时创作于 https://programmerscareer.com/zh-cn/debate-error-handling/
版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证

如何让你的Go代码容易被发现

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×