简化错误处理:可能吗?

注:本文核心内容由大语言模型生成,辅以人工事实核查与结构调整。
Go 的错误处理机制,尤其是在 Go 1.13 引入 errors.Is 和 errors.As 之后,旨在提供一种结构化且可追踪的方法。然而,尽管这是经过深思熟虑的设计,它依然是 Go 语言中 最具争议的话题之一。在泛型加入之后的各类调查中,它经常被提及为最常见的问题。这场持续的讨论凸显了 Go 社区在“改进愿望”与“坚持核心哲学”之间的紧张关系。
当前面临的挑战
Go 错误处理的主要问题,也是开发者最常抱怨的地方,就是它的 冗长与重复样板代码。模式 if err != nil { return err } 无处不在,在代码库里“数不清地”重复出现,GitHub 搜索结果也能证明这一点。
这种冗长带来了多个问题:
可读性降低:很多开发者认为,大量的错误检查掩盖了函数的主要逻辑,使得“更难看清方法到底在做什么”。有些人形容阅读这些重复代码就像是一种“负担”。
errors.As不够符合人体工学:目前的errors.As用法要求开发者必须预先声明一个目标错误类型的变量,然后传入它的指针。这被认为是“不符合人体工学”的——样板化、导致变量作用域泄漏,并且很像 C 语言那种“输出参数”,而不是 Go 一贯提倡的多返回值风格。当前的
errors.As用法:1
2
3
4
5
6
7
8err := foo()
if err != nil {
var myErr *MyCustomError
if errors.As(err, &myErr) { // myErr 的作用域会超出此代码块
// 处理 myErr
}
// ...
}开发者挫败感:社区里有些人觉得错误处理改进一直没有实质进展,导致一种“永远僵局”的苦涩感。显式处理常常被视为“繁琐笨拙”。
常见的样板代码示例:
1
2
3
4
5
6
7
8
9err := doSomethingTowardMyActualGoal()
if err != nil {
return err
}
err = doTheNextInterestingThing()
if err != nil {
return err
}
// ... 实际逻辑 ...缺乏堆栈追踪:一些用户还认为,缺少堆栈追踪功能对调试来说是个很大的痛点。
支持当前做法的论点
尽管存在上述问题,许多 Go 开发者和语言设计者依然坚决捍卫当前的错误处理哲学,并提出以下优势:
显式性与责任链:Go 的方式迫使开发者显式地处理错误,形成一种“责任传递的流程”。这带来“安心感”,并防止错误被静默忽略,或像异常那样导致意料之外的控制流问题。
清晰性与可排错性:显式错误检查让程序“超级清晰地展现出可能在哪儿出错”,这有助于排查问题。它尤其在处理“边缘场景”时有帮助——这些地方往往比“主路径”更关键、更复杂。
有意为之的设计:当前系统是一个“非常有意图的语言设计”,根本上基于这样一种信念:错误值比异常提供了更好的语义。
简单性:系统足够简单,初学者也能轻松掌握,同时又为复杂错误处理提供了必要的构建模块,符合“保持简单(Keep It Simple)”的哲学。
避免异常的陷阱:异常会隐藏控制流、展开调用栈、丢失上下文,并且在很多语言中经常被忽略。Go 的方法正是为了避免这些问题。
不是 DRY 原则的违反:有些人认为,反复写
if err != nil { return err }并不是违反了“不要重复自己(DRY)”原则,因为每一个错误检查,虽然语法类似,但语义上针对的是“完全不同的事情”。高级功能:Go 已经引入了
errors.Join(),同时errors.Is和errors.As也被认为为复杂错误处理提供了显著优势,是其他语言所不具备的。
提议的改进与替代方案
Go 社区提出了许多改进错误处理的方法,其中不少灵感来自其他语言:
错误传播的语法糖:最常见的诉求是简化样板化的
if err != nil { return err }。Rust 的
?运算符:经常被引用为理想方案,用于简洁地传播错误。Go 的等价提案可能是value, ? := myfunc(…),其中?会自动处理错误的返回。单行条件语句:允许
if err != nil return err,以减少代码的垂直空间占用。新增运算符:例如在函数调用后加上
!,表示“如果错误不为 nil 就返回”。
基于泛型的
errors.As改进(IsA/AsA):一个重要提案(Issue #51945)建议引入新的类型参数化函数AsA,替代现有的errors.As模式。优势:
提供更符合人体工学的“逗号 ok”模式(Go 开发者熟悉的风格);
将错误变量的作用域限制在必要范围;
提供 编译期类型安全(避免因错误的
target类型导致运行时 panic);显著的性能提升(快 50–70%),通过避免反射和减少堆分配实现。
提议的
errors.AsA用法:1
2
3
4
5
6
7
8
9err := someOperation()
if err != nil {
if myErr, ok := errors.AsA[*MyCustomError](err); ok { // myErr 的作用域仅在此代码块
// 处理 myErr
} else if otherErr, ok := errors.AsA[*OtherError](err); ok {
// 处理 otherErr
}
// 处理其他情况
}挑战:
AsA的返回签名(_ E, ok bool)并不能像当前errors.As那样优雅地融入switch语句。不过,这一提案已获得关注,并进入“活跃(Active)”评审阶段。
try关键字:受 Zig 等语言启发,有些提案建议为 Go 2 引入try关键字。更复杂的
try提案包括隐式跳转到当前函数中的catch:标签,以便进行错误注解或恢复,可能还会配合新的errors.Annotate()函数。但也有人反对
try直接导致立即return的行为,因为这可能会削弱错误注解的使用。
常见误解/被驳斥的观点
在争论过程中,关于 Go 错误处理的一些观点被挑战或驳斥:
“样板代码违反 DRY 原则”:尽管这些代码片段看起来相似,但每个
if err != nil { return err }实际上都是针对“不同操作”的检查,因此更像是编程结构的重复使用,而非真正的 DRY 违背。“你不一定要用它”作为新特性的理由:这一说法基本被否定,因为一旦语言新增特性,就会“污染”开源代码和团队代码,迫使所有开发者不得不接触。
“Go 社区曾经反对泛型”:虽然确实有个别人反对泛型,但更广泛的观点是希望找到一个符合 Go 设计哲学的解决方案,而最终这也确实实现了。
“快乐路径(happy path)可读性最重要”:Go 的支持者认为,让“快乐路径”过于简洁反而是“聪明代码陷阱”。Go 更注重“边缘路径和错误分支”的可读性,因为这些地方往往更复杂、更关键,也更利于调试。
“Go 的错误处理比其他语言更啰嗦”:有人指出,虽然 Go 的显式检查看似冗长,但在 Java 等语言中,正确的错误处理(包括
throws声明、try块和finally子句)通常会产生更多代码行,而且依然可能导致未处理的异常。无大括号的条件语句:比如
if err != nil return err这样的简写虽然能减少冗余,但有人反对,认为无大括号条件语句存在安全隐患,并增加语法复杂性。
用户体验
Go 的错误处理在用户体验上具有很强的主观性和差异性:
积极反馈:许多开发者表示满意,认为它“简单直接”、“线性”、“可靠”,并且“易于阅读和理解”。显式的错误处理带来了“安心感”,并能快速定位潜在的出错点。
新手挑战:对于 Go 新手而言,错误处理经常被视为一个“难点”,尤其是从使用异常机制的语言转过来时。
争议性:这一问题依然高度争议,双方立场鲜明。一些用户觉得 Go 社区在抗拒改变上过于教条。
工具辅助:像 GitHub Copilot 这样的工具被认为能减轻输入样板代码的负担。
对代码逻辑的影响:对某些人来说,冗长的错误检查“妨碍了对代码核心逻辑的理解”。
第三方库的问题:当底层库返回未导出的错误类型时,可能会遇到困难,迫使开发者退而求其次,使用基于字符串的错误检查方式。
那句“Gofmt 的风格没有人最喜欢,但 gofmt 却是所有人最喜欢的”的谚语,有时也被套用于 Go 的错误处理,意味着虽然没人完全爱它,但它确实为社区发挥了积极作用。最终,许多开发者接受了一个现实:没有任何语言能完全满足所有人的期望,而他们依然能够欣赏 Go 本身的价值。
参考
- GitHub search for boilerplate:
- Go blog survey results:
errors.Aswith type parameters GitHub issue:- Rust error handling documentation:
- Medium article on DRY misconception:
更多内容
最近文章:
随机文章:
更多该系列文章,参考medium链接:
https://wesley-wei.medium.com/list/you-should-know-in-golang-e9491363cd9a
English post: https://programmerscareer.com/go-errors-asa/
作者:微信公众号,Medium,LinkedIn,Twitter
发表日期:原文在 2025-09-10 11:19 时创作于 https://programmerscareer.com/zh-cn/go-errors-asa/
版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
评论