Golang 错误处理

go的错误处理与其他一些主流编程语言具有本质的区别,go的内嵌类型error不含有stack trace,也不支持传统的 try/catch 语句模型。go中的错误只是函数返回的一个正常的变量值,对待错误也能像对待普通变量一样,go的这种错误模型带来了更轻易、更简便的程序设计风格。

error interface

builtin.go
// 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
}
errorError

构造 error

errors.New("")fmt.Errorf("")
errors.Newfmt.Errorf

自定义error

自定义 error 只要实现 builtin.go 中的 error 接口就可以向上兼容 error。同时我们可以利用 go 嵌套接口的方法去定义一个我们自己的 error 接口:

type myErr interface {
    error
    Desc()   string
    String() string
}

再定义结构体去实现该接口,我们就有了自己定义的 custom error了。

wrap 和 unwrap

fmt.Errorf
func Errorf(format string, a ...any) error {
	p := newPrinter()
	p.wrapErrs = true
	p.doPrintf(format, a)
	s := string(p.buf)
	var err error
	if p.wrappedErr == nil {
		err = errors.New(s)
	} else {
		err = &wrapError{s, p.wrappedErr}
	}
	p.free()
	return err
}

type wrapError struct {
	msg string
	err error
}

func (e *wrapError) Error() string {
	return e.msg
}

func (e *wrapError) Unwrap() error {
	return e.err
}
wrapErrormsgerrorwrapErrorerrorerrorComposite Patternwraperrorunwraperror
Errorf%werrorwraperror
user, err := findUserById(id)
if err != nil {
  return nil, fmt.Errorf("%w find user error:%s",err, err.Error())
}
wrapunwrap

errors 包

errors/wrap.goUnwrapIsAs

Unwrap

func Unwrap(err error) error {
	u, ok := err.(interface {
		Unwrap() error
	})
	if !ok {
		return nil
	}
	return u.Unwrap()
}
errUnwrap

这个函数存在的意义在于以下:

errorerrorUnwraperrors.Unwrap

Is

func Is(err, target error) bool {
   if target == nil {
      return err == target
   }

   isComparable := reflectlite.TypeOf(target).Comparable()
   for {
      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
      }
   }
}

Is 的核心作用是为了判断传入的第一个error(err)是否 包含 第二个error(target)。判断逻辑是一种 循环 + 递归(说是递归也不太正确,内部调用的方法虽然是同名的,但不是同一个函数) 的方式:

Is(error) bool

这里的逻辑就是,err 若实现了 Is 就调用,没有就继续往下拆(获取内部的wrapped error),只要拆出来有一个 err == target 或 err 实现了 Is 并在调用后返回 true 时,整个函数就返回 true,其他情况返回 false。

As

func As(err error, target any) 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(any) bool }); ok && x.As(target) {
			return true
		}
		err = Unwrap(err)
	}
	return false
}
AsAs finds the first error in err's chain that matches target, and if one is found, sets target to that error value and returns true. Otherwise, it returns false.

也就是说,As 从传入的 error 开始遍历错误链(由Unwrap操作获取的error组成的链),寻找可为 target 赋值的error,找到就赋值并返回true,否则返回false。除了这些,函数对target的类型也有一些约束:如果是指针不能是nil,如果是结构体必须实现 error 接口等。

关于 golang 中的错误处理的知识绝对不止这些,希望大家能提出更多的想法来补充我的文档!