Go语言错误处理
Errors are values – Rob Pike
在 Go 语言中,error 是一个内置的接口类型,只有一个方法签名:Error() string ;
type error interface {
Error() string
}
errors 包提供了一个error接口的实现;并且可以通过 errors.New()方法创建一个包含错误消息的 error
func Div(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("0 cannot be a divisor")
}
return a / b, nil
}
通常定义外部调用的函数时会吧error类型作为最后一个返回值,用来处理异常情况;因为Go语言允许多返回值的特性,这样用起来会很方便。但同时也带来一个问题那就是调用方需要通过if判断来异常。
if err != nil {
// handle error
}
对于用惯了try-catch 的程序员来说,刚接触Go这一点会有些不习惯。感觉代码里到处都是 if err != nil。这一点也遭到不少人的吐槽。为此 Go语言之父 Rob Pike 在 _ 《Errors are values》_一文中专门做了解释。其中特别强调: Errors 是值类型 值可以编程,由于error就是一个值,因此它可以编程处理。当然,一个涉及error值的常见语句是判断它是否为零值,但对于error还有无数其他事情可以做,应用其中一些其他事情可以使程序变得更好,从而消除大部分用if语句检查每个错误那样的代码段。 接下来我们看一下处理error的集中方式
哨兵模式(Sentinel errors)
是一种预定义特定错误的方式,Sentinel errors愿意为使用一个特定值来表示不可能进行进一步处理的做法。所以对于 Go而言,表示使用特定的值来表示错误。比如:**io.EOF;**尽管一些标准库中有这样的用法但是使用 sentinel errors方式是最不灵活的错误处理策略,一方面 用方必须使用 == 将结果与预先声明的值进行比较,无法携带更多的上下文信息,会造成两个包直接的依赖关系。因此应该尽可能避免使用这种方式。
错误类型(Error types)
Error Types是指实现了error接口的类型,其优点是除了包含error 还可以根据需求携带其他必要的字段可以提供更多的上下文信息。标准库中fs.PathError 就是一个例子:
// PathError records an error and the operation and file path that caused it.
type PathError struct {
Op string
Path string
Err error
}
func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }
这种方式也有一定弊端,那就是调用者如果要使用类型断言和类型 switch,就得让自定义的 error 变为可导出类型。这种模型会导致和调用者产生强耦合。
不透明错误 (Opaque errors)
这种方式调用者只知道发生了错误,而不知道具体错误类型。调用者处理这种错误的方式就是一旦出错直接返回,否则继续执行。如果某些特定情况下需要根据错误做出某些行为。比如一个网络请求中,需要根据错误类型决定是否重试。这种情况下不要去判断错误类型而是判断是否实现了某个接口。例如:
拿到error后通过调用IsTemporary 判断是否重试。这个逻辑可以在不导入定义错误的包或者实际上不了解 err 的底层类型的情况下实现。
几个错误处理原则
- 通过消除错误消除错误处理 Eliminate error handling by eliminating errors
- 应该只处理一次错误,处理错误意味着检查错误值并做出单一决定。 You should only handle errors once. Handling an error means inspecting the error value, and making a single decision.
- 错误要被日志记录。The error has been logged.
- 应用程序处理错误,保证100%完整性。The application is back to 100% integrity
- 之后不再报告当前错误。The current error is not reported any longer.
- 如果无法处理错误,请包装并返回调用堆栈。If the error is not going to be handled, wrap and return up the call stack.