大家好,我是煎鱼。
对于 Go 这一门编程语言,截至目前较大争议话题仍在 if err != nil 在 Go 应用里所带来的各种繁杂代码,引起了社区很多正反方的探讨。
原本以为 Go 核心团队已经摆烂了。但最近老大哥 @Ian Lance Taylor 提出了一个新提案[1](后转为讨论[2]),引起了大量的社区交流:
图片
背景:为什么还要关注这件事?
在 Go 核心团队的视野中,现阶段对于处理 Go 错误处理的动力,个人理解至少来源于以下几个主要原因,不断地推动他们看到这件事。
第一是:社区中用代码反馈最多的,也就是在现在的 Go 应用代码中,存在较多的 if err != nil 的相关代码:
func CopyFile(src, dst string) error {
r, err := os.Open(src)
if err != nil {
return err
}
defer r.Close()
w, err := os.Create(dst)
if err != nil {
return err
}
defer w.Close()
if _, err := io.Copy(w, r); err != nil {
return err
}
if err := w.Close(); err != nil {
return err
}
}
一堆的 if err != nil 代码。
第二是:在历年的 Go 开发人员调查报告中提示 “错误处理被列为人们今天使用的最大特定挑战”。
如下图所示:
图片
在调查结果中,有提到:“在封闭问题中,回答最多的是学习如何有效地编写 Go(15%)和错误处理的冗长(13%)。”
第三是:在 Go issues 中存在大量的 Go 错误处理的各类提案,例如:
图片
- 《proposal: Go 2: onerr return[3]》
- 《proposal: Go 2: add or err: statement after function calls for error handling[4]》
- 《proposal: Go 2: Use ?variable simplify handling of multiple-return-values[5]》
太多太多太多这类提案了。我也分享过很多脑洞很大的社区错误新提案。
新提案:? 新语法
这种新语法的部分灵感来源于:Rust 的问号运算符。
? 将会吸收函数返回的错误,并在错误不为 nil 时自动返回,这与之前的 try 的提议类似。
它的不同之处在于:? 是一个显式语法元素,而不是对预先声明函数的调用,而且 ? 只能出现在语句的末尾,不能出现在表达式的中间。
基本介绍
例如,原本的 Go 错误处理代码如下:
r, err := SomeFunction()
if err != nil {
return fmt.Errorf("something failed: %v", err)
}
新提案引入新的 ? 语法,将其改写为如下:
r := SomeFunction() ? {
return fmt.Errorf("something failed: %v", err)
}
当然。如果是另外一种原本的写法:
if err := SomeFunction2(); err != nil {
return fmt.Errorf("something else failed: %v", err)
}
也依然可以改写为:
SomeFunction2() ? {
return fmt.Errorf("something else failed: %v", err)
}
? 这个用法将会吸收函数的错误结果。它引入了一个新块,如果错误结果不为 nil,则执行该块块。
在新块中,fmt.Errorf 里的标识符 err 指的是上述吸收的错误结果。也就是当块跟在 ? 后面时,它会隐式声明一个新的 err 变量。
完整例子
func Run() error {
Start() ? // returns error from Start if not nil
Wait() ? // returns error from Wait if not nil
return nil
}
func CopyFile(src, dst string) error {
r := os.Open(src) ? {
return fmt.Errorf("copy %s %s: %v", src, dst, err)
}
defer r.Close()
w := os.Create(dst) ? {
return fmt.Errorf("copy %s %s: %v", src, dst, err)
}
io.Copy(w, r) ? {
w.Close()
os.Remove(dst)
return fmt.Errorf("copy %s %s: %v", src, dst, err)
}
w.Close() ? {
os.Remove(dst)
return fmt.Errorf("copy %s %s: %v", src, dst, err)
}
}
func MustOpen(n string) *os.File {
f := os.Open(n) ? {
panic(err)
}
return f
}
func TestFileData(t *testing.T) {
f := os.Open("testfile") ? {
t.Fatal(err)
}
...
}
func CreateIfNotExist(name string) error {
f, err := os.OpenFile(name, os.O_EXCL|os.O_WRONLY, 0o666)
if errors.Is(err, fs.ErrExist) {
return nil
}
err ? // returns err if it is not nil
// write to f ...
}
社区争论
Go 错误处理机制这事,一向是正反双方都有。有支持继续保持的,也有反对的。
其中一位社区同学 @Michael Fridman 直接掏出了 Go 创始人 @Rob Pike 的演讲 PPT 语录:
该位同学表示:这一提议的优点有限 -- 它增加了另一种做事的方式,并使代码更难阅读(具有讽刺意味的是)。
其真心希望谷歌不会不惜一切代价发展 Go 语言,因为这将损害 Go 的长期用户对 Go 的喜爱。
并且表示:“让我们保持 Go 的简单和无趣”
总结
今天我们给大家带来了 Go 核心团队的错误处理新提案,老大哥 @Ian Lance Taylor 基于 Rust 的问号操作符作为灵感,设计出了 Go 的问号操作符和块的联动机制。
关于 Go 的错误处理机制,我经历很多。见到各种人做封装,还有使用 pnaic+recover 来做 error 机制的。各种方法都有。谁都不服谁。
但是目前 Go1 还是陷入了一个非常尴尬的境地,有人希望改,有人不希望改。无论如何都难以 “讨好” 全部人。
接下来就看新上任的 Go 核心团队负责人的魄力如何了。能否像以前 rsc 力推 module 一样背负骂名且用力推进。
毕竟,@Ian Lance Taylor 能提出这个提案和讨论。很有可能就是内部先讨论过的了。
参考资料
[1]提案: https://github.com/golang/go/issues/71203
[2]讨论: https://github.com/golang/go/discussions/71460
[3]proposal: Go 2: onerr return: https://github.com/golang/go/issues/32848
[4]proposal: Go 2: add or err: statement after function calls for error handling: https://github.com/golang/go/issues/33029
[5]proposal: Go 2: Use ?variable simplify handling of multiple-return-values: https://github.com/golang/go/issues/33074