Go 更优雅的错误处理

99 阅读2分钟

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,感兴趣的同学可以试试