Golang 中 Error 的设计及最佳实践

2023-12-15 13:57:03

如果你对于 Go 的 Error 设计不太熟悉也不习惯,为什么许多接口都需要返回 error 接口类型的值呢?什么时候该处理 error,什么时候该抛出 error,什么时候又该忽略 error ?Go 设计者又为什么要这样设计 error 呢?想必刚接触 Golang 的同学也会和我一样有类似的疑惑,在读了 TGPL 以及 Go Blog 相关的章节/内容后,我尝试回答一下这些问题。
在第 1 、2小节我将尝试回答 error 是什么,它是如何设计的,以及为什么这样设计。
在第 3 小节我将回答在 Coding 时,如何处理错误。

Error 是什么?
在 Go built-in 包中,Error 被设计为一个接口。

// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
   Error() string
}


Go 的设计理念是:失败(failure)只是一种常见的行为。

因此对于那些失败被视作理所当然的函数可以返回一个额外的结果——error,通常是最后一个返回值。而如果失败只有一种可能的原因,那么只需要返回一个 bool 值即可。

上述做法在 Go 的源码或接口设计中很常见。举两个例子:
以常见的 Reader 接口为例, Read 方法读取至多 len§ 个字节到 p 中,返回读取的字节数 n 和读取过程中可能发生的错误 err。

type Reader interface {
   Read(p []byte) (n int, err error)
}

当我们使用 map 时经常遇到的一种情况是:确定某个键是否在 map 中。但是 map 在该键不存在时也会返回默认值,此时可以使用带 bool 返回值的形式:

if val, ok := m["key"]; ok {
    // do something
} else {
    // do other things
}

Error 的设计
Go 的错误处理设计与其他语言的异常不同。Go 中的 error 就是一个普通的值对象,而其他语言如 Java 中的 Exception 将会造成程序控制流的终止和其他行为,Exception 与普通的值不同。虽然 Go 也有类似的异常机制 —— panic,但它仅用于报告完全无法预料的错误(可能有 Bug),而不应该是一个健壮程序应该返回的程序错误(这一点与 Java 等语言不同)。
关于 Go 为什么这样设计的原因,Kernighan 在 《The Go Programming Language》中给出解释:“The reason for this design is that exceptions tend to entangle the description of an error with the control flow required to handle it, often leading to an undesirable outcome: routine errors are reported to the end user in the form of an incomprehensible stack trace, full of information about the structure of the program but lacking intelligible context about what went wrong”。

即:因为异常会将错误的描述和处理错误的控制流纠缠在一起,通常会导致程序错误以一种难以理解的栈追踪的方式被报告给终端用户,这种方式充满了程序结构的信息,但是缺少关于哪里出错的易于理解的上下文信息。

相反,Go 程序使用普通的程序控制流机制如 if 以及 return 来对 error 作出响应,这种设计虽然要求 Gophers 更加关注错误处理逻辑,但这正是它想做到的点。即“好的程序应该考虑到所有可能的错误,并且对其进行处理”。
🤔 Go 将 error 设计为一个接口,只需要实现 Error() string 方法,返回有意义、简练的错误描述信息即可。这也使得我们可以以任何的方式来自定义错误。
Tips: 建议在底层只需要返回清晰地错误信息,每一层包裹一些重要并且简洁的上下文信息,并且最终在程序的顶层或者某一个不得不处理的层级处理该错误。
正是这种方式,在 Go 中也将这种层层包裹的错误称之为错误链。由此,在 Go 1.13 之后出现了一些新的设计以支持这种错误链的处理方式。其中最简单的错误链就是如下所述的层层包裹的文本信息(或者程序调用栈信息)
arduino复制代码genesis: crashed: no parachute: G-switch failed: bad relay orientation

文章来源:https://blog.csdn.net/ldxxxxll/article/details/135014496
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。