在可预见的未来,Go 不会优化错误处理了

陪她去流浪 桃子 阅读次数:500

刚看了一眼 Go 团队最新发表的关于错误处理的文章。结论就一点:

在可预见的未来,Go 语言对错误(errors)的处理方式都不会有变化了

所有类似“try/catch”、“check/handle”、“?(问号表达式)”的提案都被否决🙅了。

作者甚至认为:

  1. “Go 已经走过了 15 年,太老了,不想修改语言的语法了”
  2. “IDE 在大语言模型的加持下,也已经足够好用了”
  3. ...

总之,我就感觉……挺遗憾、难过的。

我自己的简易处理器

我其实也不太喜欢 Go 里面大量的:

1
2
3
4
_, err := call()
if err != nil {
    return err
}

特别是看到类似 AWS-SDK 中这种同时出现 3、50 个 if err != nil 的“拉屎💩”写法时,我内心是非常崩溃😫的(即便代码是自动生成的)。

等不到天亮,我只有自己克服了。

一套算是比较泛型的类 try/catch/throw 写法。

遇到问题直接抛出“异常”

最早在 Go 中遇到错误直接 panic的做法我应该是从 html/template1 中学来的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Must is a helper that wraps a call to a function returning ([*Template], error)
// and panics if the error is non-nil. It is intended for use in variable initializations
// such as
//
//	var t = template.Must(template.New("name").Parse("html"))
func Must(t *Template, err error) *Template {
	if err != nil {
		panic(err)
	}
	return t
}

泛型出现后,就能写出稍微更通用一点的“Must”模板函数了23

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func Must(e error) {
	if e != nil {
		panic(e)
	}
}
func Must1[A any](a A, e error) A {
	if e != nil {
		panic(e)
	}
	return a
}

捕捉“异常”

所有被抛出的东西都被当作错误(error):

1
2
3
4
5
6
7
8
9
func CatchAsError(err *error) {
	if er := recover(); er != nil {
		if er2, ok := er.(error); ok {
			*err = er2
			return
		}
		*err = fmt.Errorf(`%v`, er)
	}
}

“异常”不会被抛得太远,通常会在函数的第一行defer 处理掉:

1
2
3
4
5
func call() (_ int, outErr error) {
	defer CatchAsError(&outErr)
	n := Must1(someFunc())
	return n, nil
}

当一个函数中需要处理的错误越多时,这种写法的代码的清晰度可谓立竿见影。

最后

如上面的文章以及评论所言,这种做法确实改变了控制流。但是写起来爽啊!那就行了。

我自创4的这种写法也不“完美”,比如 Must 的时候:不能附加自定义的错误消息。

当然,还是希望 Go 团队不要觉得自己“老”了,不再尝试更新了。


  1. go/src/html/template/template.go at master · golang/go ^

  2. taoblog/modules/utils/generic.go at master · movsb/taoblog ^

  3. 虽然做不到 C++ 里面的函数重载完美转发💯以及右值引用,好在“够用”。另外,Go 语言也不太看得起这些“复杂”“玩意儿”。 ^

  4. 我想到这种写法的时候确实没有参考别人。但是,社区当然会有同样做法的人,比如这条评论:issues/73376。 ^