Golang的错误处理error

187 阅读6分钟

error是什么

error是golang的错误处理机制,error接口定义如下:

type error interface {
   Error() string
}

创建error

创建方式有两种:

  1. errors.New()

  2. fmt.Errorf()

errors.New

示例:

func main() {
   err := errors.New("这是一个error")
   fmt.Printf("type:%T val:%v\n", err, err) // 输出结果:type:*errors.errorString val:这是一个error
}

errors.New会创建errorString结构体实例,返回其指针,源码如下:

// New returns an error that formats as the given text.
// Each call to New returns a distinct error value even if the text is identical.
func New(text string) error {
   return &errorString{text}
}

// errorString is a trivial implementation of error.
type errorString struct {
   s string
}

func (e *errorString) Error() string {
   return e.s
}

fmt.Errorf

示例:

func main() {
   err1 := errors.New("这是一个error")
   fmt.Println("err1:", err1) // 输出结果:err1: 这是一个error

   err2 := fmt.Errorf("这是第%d个error", 2)
   fmt.Printf("err2Type:%T err2Val:%v\n", err2, err2) // 输出结果:err2Type:*errors.errorString err2Val:这是第2个error

   err3 := fmt.Errorf("这是第%d个error,嵌套err:%w", 3, err1)
   fmt.Printf("err3Type:%T err3Val:%v\n", err3, err3) // 输出结果:err3Type:*fmt.wrapError err3Val:这是第3个error,嵌套err:这是一个error
   
    err4 := fmt.Errorf("这是第%d个error,嵌套err1:%w,嵌套err2:%w", 4, err1, err2)
    fmt.Printf("err4Type:%T err4Val:%v\n", err4, err4) // 输出结果:err4Type:*errors.errorString err4Val:这是第4个error,嵌套err1:这是一个error,嵌套err2:%!w(*errors.errorString=&{这是第2个error})   
}

可以看到fmt.Errorf()可以返回两种error类型:

  1. *errors.errorString
  2. *fmt.wrapError

具体源码:

源码解读可以 fmt 格式化输出源码,是一模一样的

// Errorf formats according to a format specifier and returns the string as a
// value that satisfies error.
//
// If the format specifier includes a %w verb with an error operand,
// the returned error will implement an Unwrap method returning the operand. It is
// invalid to include more than one %w verb or to supply it with an operand
// that does not implement the error interface. The %w verb is otherwise
// a synonym for %v.
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
}

fmt.wrapError类型:

type wrapError struct {
   msg string
   err error
}

// 实现了error接口
func (e *wrapError) Error() string {
   return e.msg
}

// 实现了xerrors.Wrapper接口
func (e *wrapError) Unwrap() error {
   return e.err
}

xerrors.Wrapper接口:

// A Wrapper provides context around another error.
type Wrapper interface {
   // Unwrap returns the next error in the error chain.
   // If there is no next error, Unwrap returns nil.
   Unwrap() error
}

注释中的关键字:the error chain,说明error是一个链式模型(链表) ,这也是实际项目中使用error的常见形式

链式模型—error chain

xerrors.Wrapper接口定义:

// A Wrapper provides context around another error.
type Wrapper interface {
   // Unwrap returns the next error in the error chain.
   // If there is no next error, Unwrap returns nil.
   Unwrap() error
}

什么是链式模型?什么是 the error chain

简单理解就是:嵌套、套娃,一个error嵌套另一个error,一直嵌套下去...


示例:

func main() {
   err1 := errors.New("这是一个error")
   err2 := fmt.Errorf("这是第%d个error,嵌套err1:%w", 2, err1)
   err3 := fmt.Errorf("这是第%d个error,嵌套err2:%w", 3, err2)
   err4 := fmt.Errorf("这是第%d个error,嵌套err3:%w", 4, err3)

   fmt.Println(err4)

   unwrapped1 := errors.Unwrap(err4)
   fmt.Println(unwrapped1)

   unwrapped2 := errors.Unwrap(unwrapped1)
   fmt.Println(unwrapped2)

   unwrapped3 := errors.Unwrap(unwrapped2)
   fmt.Println(unwrapped3)

   unwrapped4 := errors.Unwrap(unwrapped3)
   fmt.Println(unwrapped4)
}

输出结果:

这是第4error,嵌套err3:这是第3error,嵌套err2:这是第2error,嵌套err1:这是一个error
这是第3error,嵌套err2:这是第2error,嵌套err1:这是一个error
这是第2error,嵌套err1:这是一个error
这是一个error
<nil>

errors.Unwrap方法源码

// Unwrap returns the result of calling the Unwrap method on err, if err's
// type contains an Unwrap method returning error.
// Otherwise, Unwrap returns nil.
func Unwrap(err error) error {
   u, ok := err.(interface {
      Unwrap() error
   })
   if !ok {
      return nil
   }
   return u.Unwrap()
}

比较error——errors.Is()

示例:

func main() {
   err1 := errors.New("这是一个error")

   err2 := fmt.Errorf("这是第%d个error,嵌套err1:%w", 2, err1)

   err3 := fmt.Errorf("这是第%d个error,嵌套err2:%w", 3, err2)

   err4 := fmt.Errorf("这是第%d个error,嵌套err3:%w", 4, err3)

   fmt.Println(err4)

   unwrapped1 := errors.Unwrap(err4)
   fmt.Println(unwrapped1)

   unwrapped2 := errors.Unwrap(unwrapped1)
   fmt.Println(unwrapped2)

   unwrapped3 := errors.Unwrap(unwrapped2)
   fmt.Println(unwrapped3)

   unwrapped4 := errors.Unwrap(unwrapped3)
   fmt.Println(unwrapped4)

   ok1 := errors.Is(err4, err3)
   ok2 := errors.Is(err4, err2)
   ok3 := errors.Is(err4, err1)
   ok4 := errors.Is(err4, nil)
   ok5 := errors.Is(err4, errors.New("这是一个error"))
   fmt.Println(ok1, ok2, ok3, ok4, ok5) // 输出:true true true false false
}

输出结果:

这是第4error,嵌套err3:这是第3error,嵌套err2:这是第2error,嵌套err1:这是一个error
这是第3error,嵌套err2:这是第2error,嵌套err1:这是一个error
这是第2error,嵌套err1:这是一个error
这是一个error
<nil>
true true true false false

errors.Is()源码

重点:reports whether any error in err's chain matches target. 会判断入参err的error链中是否有error与入参target匹配。

// Is reports whether any error in err's chain matches target.
//
// The chain consists of err itself followed by the sequence of errors obtained by
// repeatedly calling Unwrap.
//
// An error is considered to match a target if it is equal to that target or if
// it implements a method Is(error) bool such that Is(target) returns true.
//
// An error type might provide an Is method so it can be treated as equivalent
// to an existing error. For example, if MyError defines
//
// func (m MyError) Is(target error) bool { return target == fs.ErrExist }
//
// then Is(MyError{}, fs.ErrExist) returns true. See syscall.Errno.Is for
// an example in the standard library. An Is method should only shallowly
// compare err and the target and not call Unwrap on either.
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
      }
      // TODO: consider supporting target.Is(err). This would allow
      // user-definable predicates, but also may allow for coping with sloppy
      // APIs, thereby making it easier to get away with them.
      if err = Unwrap(err); err == nil {
         return false
      }
   }
}

核心:

  1. reflectlite.TypeOf(target).Comparable(),判断类型是否可比较,指针是可比较的。
  2. if x, ok := err.(interface{ Is(error) bool } ); ok && x.Is(target) { return true },判断是否实现了interface{ Is(error) bool })这个接口,然后调用Is()方法比较。
  3. if err = Unwrap(err) ; err == nil { return false },继续判断err的error链中是否有error与target匹配。

项目中常见用法

需求

有时候想要针对特定error进行特殊处理,而不是遇到error就抛出。

关键:要能比较error是否是指定error

一般用法

1 事先创建好指定error

做法:

  1. 首先,定义好全局error
  2. 然后,使用==errors.Is来判断error是否是指定error,是则执行指定处理逻辑

示例

var (
   ErrorCase1 = errors.New("error case 1")
   ErrorCase2 = errors.New("error case 2")
   ErrorCase3 = errors.New("error case 3")
)

func main() {
   err := DoSomething()
   fmt.Println("raw err:", err)

   // 实际是比较指针
   if err == ErrorCase1 {
      fmt.Println("case1 process...")
      return
   }

   // 也可用errors.Is方法判断
   if errors.Is(err, ErrorCase2) {
      fmt.Println("case2 process...")
      return
   }

   if err == ErrorCase3 {
      fmt.Println("case3 process...")
      return
   }
}

func DoSomething() error {
   fmt.Println("do something...")
   rand.Seed(time.Now().UnixMilli())
   i := rand.Int()
   if i%3 == 0 {
      return ErrorCase1
   } else if i%3 == 1 {
      return ErrorCase2
   } else {
      return ErrorCase3
   }
}

gorm框架举例

gorm框架事先定义好的error举例:

// ErrRecordNotFound record not found error
var ErrRecordNotFound = errors.New("record not found")

var (
   // ErrRecordNotFound record not found error
   ErrRecordNotFound = logger.ErrRecordNotFound
   // ErrInvalidTransaction invalid transaction when you are trying to `Commit` or `Rollback`
   ErrInvalidTransaction = errors.New("invalid transaction")
   // ErrNotImplemented not implemented
   ErrNotImplemented = errors.New("not implemented")
   // ErrMissingWhereClause missing where clause
   ErrMissingWhereClause = errors.New("WHERE conditions required")
   // ErrUnsupportedRelation unsupported relations
   ErrUnsupportedRelation = errors.New("unsupported relations")
   // ErrPrimaryKeyRequired primary keys required
   ErrPrimaryKeyRequired = errors.New("primary key required")
   
   ......
)

2 自定义error,使用code唯一标识error

  1. 首先,自定义error:
// 继承、扩展error接口
type IError interface {
   error

   GetCode() int
   GetMsg() string
}

// 实现IError接口
type BizError struct {
   Code int
   Msg  string
}

func NewBizError(Code int, Msg string) *BizError {
   e := &BizError{
      Code: Code,
      Msg:  Msg,
   }

   return e
}

func (b *BizError) Error() string {
   return b.Msg
}

func (b *BizError) GetCode() int {
   return b.Code
}

func (b *BizError) GetMsg() string {
   return b.Msg
}

// Is 判断error是否相等
func (b *BizError) Is(err error) bool {
    err0, ok := err.(*BizError)
    if ok {
        return err0.GetCode() == b.GetCode()
    }
    return false
}
  1. 然后,定义全局error唯一标识,用来表明特定场景的error:
const (
   ErrorCode1 = 1001
   ErrorCode2 = 1002
   ErrorCode3 = 1003
)
  1. 然后,使用:
func main() {
   err := DoSomething()
   fmt.Println("raw err:", err)

   if err == nil {
      fmt.Println("no err")
      return
   }

   ie, ok := err.(IError)
   if !ok {
      fmt.Println("not bizError. process system error...")
      return
   }

   if ie.GetCode() == ErrorCode1 {
      fmt.Println("error case1 process...")
      return
   }
   if ie.GetCode() == ErrorCode2 {
      fmt.Println("error case2 process...")
      return
   }
   if ie.GetCode() == ErrorCode3 {
      fmt.Println("error case3 process...")
      return
   }
}

func DoSomething() error {
   fmt.Println("do something...")
   rand.Seed(time.Now().UnixMilli())
   i := rand.Int()

   if i%4 == 0 {
      return errors.New("system error")
   }

   if i%4 == 1 {
      return NewBizError(ErrorCode1, "error case 1")
   }

   if i%4 == 2 {
      return NewBizError(ErrorCode2, "error case 2")
   }

   if i%4 == 3 {
      return NewBizError(ErrorCode3, "error case 3")
   }

   return nil
}