Error处理
种类
- Sentinel Error(哨兵 Error)
- Error Type
- Opaque errors(不透明错误)
Sentinel Error
说明
预定义的特定错误,我们叫为 sentinel error,这个名字来源于计算机编程中使用一个特定值来表示不可能进行进一步处理的做法。例如:
- io.EOF
- syscall.ENOENT
自定义Sentinel Error
var EOF = errors.New("EOF")
缺点
-
灵活性低。使用 sentinel 值是最不灵活的错误处理策略,因为调用方必须使用 == 将结果与预先声明的值进行比较。当您想要提供更多的上下文时,这就出现了一个问题,因为返回一个不同的错误将破坏相等性检查。甚至是一些有意义的 fmt.Errorf 携带一些上下文,也会破坏调用者的 == ,调用者将被迫查看 error.Error() 方法的输出,以查看它是否与特定的字符串匹配。
-
增加 API 的表面积。如果您的公共函数或方法返回一个特定值的错误,那么该值必须是公共的,当然要有文档记录,这会增加 API 的表面积。如果 API 定义了一个返回特定错误的 interface,则该接口的所有实现都将被限制为仅返回该错误,即使它们可以提供更具描述性的错误。比如 io.Reader。像 io.Copy 这类函数需要 reader 的实现者比如返回 io.EOF 来告诉调用者没有更多数据了,但这又不是错误。
-
两个包之间创建了依赖。Sentinel Errors 最糟糕的问题是它们在两个包之间创建了源代码依赖关系。例如,检查错误是否等于 io.EOF,您的代码必须导入 io 包。这个特定的例子听起来并不那么糟糕,因为它非常常见,但是想象一下,当项目中的许多包导出错误值时,存在耦合,项目中的其他包必须导入这些错误值才能检查特定的错误条件(in the form of an import loop)。
使用建议
- 不要依赖检查 error.Error 的输出。Error 方法存在于 error 接口主要用于方便程序员使用,但不是程序(编写测试可能会依赖这个返回)。这个输出的字符串用于记录日志、输出到 stdout 等。
- 避免在业务代码中使用 sentinel errors。在标准库中有一些使用它们的情况,但这不是一个比较符合业务代码应该模仿的模式。
Error Type
说明
与Sentinel Error相比,Error Type的一大改进是它们能够包装底层错误以提供更多上下文。
一个不错的例子就是 os.PathError 它提供了底层执行了什么操作、那个路径出了什么问题。
type PathError = fs.PathError
type PathError struct {
Op string
Path string
Err error
}
使用建议
调用者要使用类型断言和类型 switch,就要让自定义的 error 变为 public。这种模型会导致和调用者产生强耦合,从而导致 API 变得脆弱。尽量避免使用 error types,虽然错误类型比 sentinel errors 更好,因为它们可以捕获关于出错的更多上下文,但是 error types 共享 error values 许多相同的问题。或者至少避免将它们作为公共 API 的一部分。
Opaque errors(不透明错误)
说明
不透明错误的一个很重要的原则:错误应该代表特定的行为而不是特定的的值。
例如一个网络请求的错误 net.Error
,它的值可以表示超时、找不到资源等等。而判断具体的错误不应该根据值判断,而是定义一系列的方法来判断是何种行为。
package net
// 定义error
type Error interface {
error
Timeout() bool // Is a timeout error
NotFound() bool // Is a 404 error
}
// 判断error
if nerr,ok:=err.(net.Error);ok && nerr.Timeout(){
}
使用建议
建议使用 Opaque errors,因为它要求代码和调用者之间的耦合最少。作为调用者,关于操作的结果,所知道的就是它起作用了,或者没有起作用(成功还是失败)。关键是,这个可以在不导入定义错误的包或者实际上不了解 err 的底层类型的情况下实现——我们只对它的行为感兴趣。