Go 的开发人员对 error 处理肯定不陌生,因为在 Go 的设计哲学里面,错误就是应该被处理的而不是被忽略,以至于现在提到 Go,就会说 if err != nil,但是实际上目前 error 是比较简单的,它只是一个接口,只能存储简单的字符串,所以在实际的项目中,通常有两种方式来处理 error,一种是使用 Errorf进行拼接 newErr := fmt.Errorf("error %v, err) 但是这种会丢失原始的错误类型,不能再进行比较等操作了,还有一种是我们会自定义 error struct 来存储额外的记录
type MyError struct{
err error
msg string
code string
}
但是这种方式也不通用,因为每个项目都可能有自己的 struct,那么有没有一种更通用更简单的方式呢?答案就是 warp error
fmt.Errorf
首先可以使用fmt.Errorf(%w)格式符来嵌套 error,这样就避免引入额外的方法,非常简洁
Unwrap
对于嵌套的 error 我们可以使用 Unwrap 来解析
func Unwrap(err error) error {
u, ok := err.(interface {
Unwrap() error // 判断是否实现unwrap方法
})
if !ok {
return nil
}
return u.Unwrap()
}
Is
可以使用 Is 函数来判断某个错误是否和某个特定错误相等
func Is(err, target error) bool {
if target == nil {
return err == target
}
isComparable := reflectlite.TypeOf(target).Comparable()
for { // 通过无限循环来unwrap错误并进行比较
if isComparable && err == target {
return true
}
if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
return true
}
if err = Unwrap(err); err == nil {
return false
}
}
}
As
还可以通过 As 函数从错误链条中找到 target 的类型并设置
func As(err error, target interface{}) bool {
if target == nil {
panic("errors: target cannot be nil")
}
val := reflectlite.ValueOf(target)
typ := val.Type()
if typ.Kind() != reflectlite.Ptr || val.IsNil() {
panic("errors: target must be a non-nil pointer")
}
targetType := typ.Elem()
if targetType.Kind() != reflectlite.Interface && !targetType.Implements(errorType) {
panic("errors: *target must be interface or implement error")
}
for err != nil {
if reflectlite.TypeOf(err).AssignableTo(targetType) {
val.Elem().Set(reflectlite.ValueOf(err))
return true
}
if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) {
return true
}
err = Unwrap(err)
}
return false
}
综上就是 error wrap 的一些相关函数和方法,它极大地增强了 Go 原本孱弱的 error,感兴趣的同学可以试试