Go的错误处理

Go 的错误处理

Created: December 7, 2021 6:24 PM
Tags: Golang
Published: December 29, 2021

问题

Go 语言一个很让人烦恼的问题就是 error 的处理。

每一个函数都会要返回一个 error，每次返回都需要进行一次判断，导致一段核心调用的代码逻辑段，超过一半的行数都是在处理 error，而且每次处理的代码还是完全一样的，既冗余又无意义，对于之前接受了 Python 简洁哲学熏陶的我来说，是一种极大的痛苦。

好在这个问题也不是无法优化的。当然，更好的可读性一定会牺牲一定的性能，但是介于 Go 本身的性能已经极为出色，而其可读性已经是个尾大不掉的问题了。在处理逻辑较为复杂的地方，使用该方式，来减少 Go 的 error 处理代码，可以说是利大于弊的。

解决方案

下面我们来示范一下，

Copy
// 我们先来一段代码

// 处理string判断的被调用函数
func A(a, b string) (string, error) {
if a != b {
return "", errors.NewErr("info")
}
return a, nil
}

// 处理int判断的被调用函数
func B(a, b int) (int, error) {
if a != b {
return 0, errors.NewErr("info")
}
return a, nil
}

// 主函数
func Main(a, b string, c, d int) error {
// 普通写法之下，每次调用都需要处理一次error
res, err := A(a, b)
if err != nil {
return err
}
res, err = B(c, d)
if err != nil {
return err
}
// 此处略去100次调用
return nil
}

从上面这段代码，可以看出，每次主函数，调用一次其他函数，就需要处理一次返回，一行调用，三行错误处理，调用的逻辑完全淹没在了错误处理中。

我们的解决办法就是修改一下被调用的函数，让他们也学会处理一下 error，将主函数的错误处理，移动到每个函数中，这样，这个函数复用的次数越多，节省的代码就越多。而且可读性会有很好的提升。

下面我来示范一下

Copy
func A(a, b string, err error) (string, error) {
// 增加了一个字段err，和以下逻辑
// 接收一个error参数，如果非空就返回
if err != nil {
return "", err
}
if a != b {
return "", errors.NewErr("info")
}
return a, nil
}

func B(a, b int) (int, error) {
if err != nil {
return 0, nil
}
if a != b {
return 0, errors.NewErr("info")
}
return a, nil
}

func Main(a, b string, c, d int) error {
res, err := A(a, b, nil)
res, err = B(c, d, err)
// 此处略去100次调用
// 可以看出这里无论调用多少次，只用写一次错误处理
if err != nil {
return err
}
return nil
}

反思

那么这个样式是否可以用在所有的地方来解决我们的错误处理问题呢？

我觉得这并不是万能的处理方法，它至少有一下几个问题

虽然一旦其中一个函数出现了 error，后续的函数其实不会真的执行，但是还是存在函数调用就参数传递，所以一定是有一定的性能损耗的，如果我们追求极致性能的部分，不适宜用这种方式。

这种方式，会增加一些 debug 定位的难度，因为是执行完所有的逻辑后，才返回错误信息，所以不能以很直观的方式来展示具体错误的地方。当然，也是有解决办法的，我们可以动过给 error message 里加入函数堆栈信息，但是就略麻烦了一些。

总结

这种错误处理方式，可以很好地提升我们的代码可读性，但是会对其性能造成细微的影响，并且会增加一点 debug 的难度。但是对于较为复杂的代码，这点损失来换回高可读性，我个人认为是值得的。