Go 1.19.3 error原理简析

Go error是一个很痛的话题(真心难用)

标准库 error 的定义

// 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
}

error 是一个内置的接口,它拥有一个Error() string方法,实现该方法即实现了error接口。

标准库errors包

// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package errors implements functions to manipulate errors.
//
// The New function creates errors whose only content is a text message.
//
// The Unwrap, Is and As functions work on errors that may wrap other errors.
// An error wraps another error if its type has the method
//
//	Unwrap() error
//
// If e.Unwrap() returns a non-nil error w, then we say that e wraps w.
//
// Unwrap unpacks wrapped errors. If its argument's type has an
// Unwrap method, it calls the method once. Otherwise, it returns nil.
//
// A simple way to create wrapped errors is to call fmt.Errorf and apply the %w verb
// to the error argument:
//
//	errors.Unwrap(fmt.Errorf("... %w ...", ..., err, ...))
//
// returns err.
//
// Is unwraps its first argument sequentially looking for an error that matches the
// second. It reports whether it finds a match. It should be used in preference to
// simple equality checks:
//
//	if errors.Is(err, fs.ErrExist)
//
// is preferable to
//
//	if err == fs.ErrExist
//
// because the former will succeed if err wraps fs.ErrExist.
//
// As unwraps its first argument sequentially looking for an error that can be
// assigned to its second argument, which must be a pointer. If it succeeds, it
// performs the assignment and returns true. Otherwise, it returns false. The form
//
//	var perr *fs.PathError
//	if errors.As(err, &perr) {
//		fmt.Println(perr.Path)
//	}
//
// is preferable to
//
//	if perr, ok := err.(*fs.PathError); ok {
//		fmt.Println(perr.Path)
//	}
//
// because the former will succeed if err wraps an *fs.PathError.
package errors

// 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
}

errors.New(text string) error,创建一个error对象

errors 包中定义了 errorString类型,其实现了error接口,errors.New(text string)函数,根据传入字符串返回一个error对象,我们可以看出,其返回值是指针类型,这是为了区分error,即使两个传入的text文本一致,那么返回的值也是完全不同的。

errors.Unwrap(err error) error 拆箱error

如果传入的error实现了

interface {
   Unwrap() error
}

接口,则调用Unwrap方法对传入的error进行拆箱,并返回拆箱后的error,否则返回nil

errors.Is(err, target error) bool 判定err是否为target error

该函数会调用Unwrap方法,获取最底层的错误原因

errors.As(err error, target any) bool 判定err链中是否包含传入的target,若包含则用target带出来

// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package errors

import (
	"internal/reflectlite"
)

// 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()
}

// 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
		}
	}
}

// As 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.
//
// The chain consists of err itself followed by the sequence of errors obtained by
// repeatedly calling Unwrap.
//
// An error matches target if the error's concrete value is assignable to the value
// pointed to by target, or if the error has a method As(interface{}) bool such that
// As(target) returns true. In the latter case, the As method is responsible for
// setting target.
//
// An error type might provide an As method so it can be treated as if it were a
// different error type.
//
// As panics if target is not a non-nil pointer to either a type that implements
// error, or to any interface type.
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
}

var errorType = reflectlite.TypeOf((*error)(nil)).Elem()

Go 1.13 fmt.Errorf %w谓词,包装error

   err := fmt.Errorf("access denied: %w", ErrPermission) // 加入上下文信息
   // ...
   if errors.Is(err,ErrPermission) { //可以用Is 和 As 进行判定
    // ...
   }
   var err1 *ErrPermission
   if errors.As(err,&ErrPermission) {
    // ...
   }

令人不解的是,直接在errors中加入Wrap函数,统一API不香吗?

Error的几种处理形式

Sentinel errors 哨兵error

   var EOF = errors.New("EOF")

包级别的预定义的Error称之为哨兵error,这种error无法携带更多的上下文信息,并且需要包中暴露更多的API,容易产生更多的依赖,不建议使用。

Error types 自定义Error类型

可以返回

type MyError struct {
	Message  string
	FileName string
	FileLine int
}

func (m *MyError) Error() string {
	return fmt.Sprintf("message:%s,file:%s,line:%d", m.Message, m.FileName, m.FileLine)
}

func forTest() error {
	return &MyError{
		Message:  "not found ... ",
		FileName: "msg.go",
		FileLine: 100,
	}
}
 

检测error的时候可以使用断言的方式

switch forTest().(type) {
case nil:
	//...
case *MyError:
	//...
default:
	//...
}

if err, ok := forTest().(*MyError); ok {
   // do something ...
}   

标准库中也有很多使用自定义Error类型的场景
如: src/io/fs.go

// 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() }

func (e *PathError) Unwrap() error { return e.Err }

自定义的Error类型虽然能携带更多的上下文信息,但是其与哨兵error类似的是,依然会产生大量API的暴露,增加了维护的心智负担,所以依然不建议使用。

Opaque errors 不透明的error处理

不透明的error处理即只使用if err != nil去判断,而不关心其底层error的类型与值。调用者可以更加专注于函数的返回结果。这种error处理方式限制了上下文信息的携带。

func fn() error {
   result, err := Function()
   if err != nil {
      return err
   }
   // use result ...    
}
一种使用姿势

Assert errors for behaviour, not type 断言成一种行为,而非强类型
定义一个包内可见的接口

    type readAble interface{
        CanRead() bool
    }

定义一个包级别的函数作为暴露的API

    type IsCanRead(err error) bool {
        r, ok := err.(readAble)
        return ok && r.CanRead()
    }

error的优雅处理姿势

统计io.Reader读取内容的行数

func CountLines(r io.Reader) (int, error) {
   var (
      br = bufio.NewReader(r)
      lines int
      err error
   )
   for {
       _, err = br.ReadString('\n')
       lines++
       if err != nil {
          break
       }  
   }
   if err != io.EOF {
      return 0, err
   }
   return lines, nil
}

改进版本,利用bufio的Scanner

func CountLines(r io.Reader) (int, error) {
   sc := bufio.NewScanner(r)
   lines := 0
   for sc.Scan() {
      lines++
   }
   return lines, sc.Err()
}

在这里插入图片描述
对上述代码的改进
在这里插入图片描述
在这里插入图片描述

Wrap Errors 打包Error

   err = fmt.Errorf("msg: %v",err)

github.com/pkg/errors 这个库用起来十分舒适,Go1.13+标准库errors参照了该库的设计理念,该库可以支持携带堆栈信息,同时pkg/errors兼容了Go1.13+ 的Unwrap,Is,As方法。

// Package errors provides simple error handling primitives.
//
// The traditional error handling idiom in Go is roughly akin to
//
//     if err != nil {
//             return err
//     }
//
// which when applied recursively up the call stack results in error reports
// without context or debugging information. The errors package allows
// programmers to add context to the failure path in their code in a way
// that does not destroy the original value of the error.
//
// Adding context to an error
//
// The errors.Wrap function returns a new error that adds context to the
// original error by recording a stack trace at the point Wrap is called,
// together with the supplied message. For example
//
//     _, err := ioutil.ReadAll(r)
//     if err != nil {
//             return errors.Wrap(err, "read failed")
//     }
//
// If additional control is required, the errors.WithStack and
// errors.WithMessage functions destructure errors.Wrap into its component
// operations: annotating an error with a stack trace and with a message,
// respectively.
//
// Retrieving the cause of an error
//
// Using errors.Wrap constructs a stack of errors, adding context to the
// preceding error. Depending on the nature of the error it may be necessary
// to reverse the operation of errors.Wrap to retrieve the original error
// for inspection. Any error value which implements this interface
//
//     type causer interface {
//             Cause() error
//     }
//
// can be inspected by errors.Cause. errors.Cause will recursively retrieve
// the topmost error that does not implement causer, which is assumed to be
// the original cause. For example:
//
//     switch err := errors.Cause(err).(type) {
//     case *MyError:
//             // handle specifically
//     default:
//             // unknown error
//     }
//
// Although the causer interface is not exported by this package, it is
// considered a part of its stable public interface.
//
// Formatted printing of errors
//
// All error values returned from this package implement fmt.Formatter and can
// be formatted by the fmt package. The following verbs are supported:
//
//     %s    print the error. If the error has a Cause it will be
//           printed recursively.
//     %v    see %s
//     %+v   extended format. Each Frame of the error's StackTrace will
//           be printed in detail.
//
// Retrieving the stack trace of an error or wrapper
//
// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are
// invoked. This information can be retrieved with the following interface:
//
//     type stackTracer interface {
//             StackTrace() errors.StackTrace
//     }
//
// The returned errors.StackTrace type is defined as
//
//     type StackTrace []Frame
//
// The Frame type represents a call site in the stack trace. Frame supports
// the fmt.Formatter interface that can be used for printing information about
// the stack trace of this error. For example:
//
//     if err, ok := err.(stackTracer); ok {
//             for _, f := range err.StackTrace() {
//                     fmt.Printf("%+s:%d\n", f, f)
//             }
//     }
//
// Although the stackTracer interface is not exported by this package, it is
// considered a part of its stable public interface.
//
// See the documentation for Frame.Format for more details.
package errors

import (
	"fmt"
	"io"
)

errors.New(message string) error 创建error

// New returns an error with the supplied message.
// New also records the stack trace at the point it was called.
func New(message string) error {
	return &fundamental{
		msg:   message,
		stack: callers(),
	}
}

errors.Errorf(format string, args …interface{}) error 格式化Error信息

// Errorf formats according to a format specifier and returns the string
// as a value that satisfies error.
// Errorf also records the stack trace at the point it was called.
func Errorf(format string, args ...interface{}) error {
	return &fundamental{
		msg:   fmt.Sprintf(format, args...),
		stack: callers(),
	}
}

fundamental 包内结构体,包含错误信息和堆栈结构

// fundamental is an error that has a message and a stack, but no caller.
type fundamental struct { //基础结构
	msg string
	*stack
}

func (f *fundamental) Error() string { return f.msg } // 实现error接口

func (f *fundamental) Format(s fmt.State, verb rune) { // 实现Formatter接口支持 %v,%+v,%s,%q几个谓词
	switch verb {
	case 'v':
		if s.Flag('+') {
			io.WriteString(s, f.msg)
			f.stack.Format(s, verb)
			return
		}
		fallthrough
	case 's':
		io.WriteString(s, f.msg)
	case 'q':
		fmt.Fprintf(s, "%q", f.msg)
	}
}

WithStack打包堆栈信息

// WithStack annotates err with a stack trace at the point WithStack was called.
// If err is nil, WithStack returns nil.
func WithStack(err error) error {
	if err == nil {
		return nil
	}
	return &withStack{
		err,
		callers(),
	}
}

type withStack struct {
	error
	*stack
}

func (w *withStack) Cause() error { return w.error }

// Unwrap provides compatibility for Go 1.13 error chains.
func (w *withStack) Unwrap() error { return w.error }

func (w *withStack) Format(s fmt.State, verb rune) {
	switch verb {
	case 'v':
		if s.Flag('+') {
			fmt.Fprintf(s, "%+v", w.Cause())
			w.stack.Format(s, verb)
			return
		}
		fallthrough
	case 's':
		io.WriteString(s, w.Error())
	case 'q':
		fmt.Fprintf(s, "%q", w.Error())
	}
}

errors.func Wrap(err error, message string) error 打包自定义的信息

// Wrap returns an error annotating err with a stack trace
// at the point Wrap is called, and the supplied message.
// If err is nil, Wrap returns nil.
func Wrap(err error, message string) error {
	if err == nil {
		return nil
	}
	err = &withMessage{
		cause: err,
		msg:   message,
	}
	return &withStack{
		err,
		callers(),
	}
}

errors.func Wrapf 打包格式化的自定义的信息

// Wrapf returns an error annotating err with a stack trace
// at the point Wrapf is called, and the format specifier.
// If err is nil, Wrapf returns nil.
func Wrapf(err error, format string, args ...interface{}) error {
	if err == nil {
		return nil
	}
	err = &withMessage{
		cause: err,
		msg:   fmt.Sprintf(format, args...),
	}
	return &withStack{
		err,
		callers(),
	}
}

withMessage 打包自定义信息

// WithMessage annotates err with a new message.
// If err is nil, WithMessage returns nil.
func WithMessage(err error, message string) error {
	if err == nil {
		return nil
	}
	return &withMessage{
		cause: err,
		msg:   message,
	}
}

// WithMessagef annotates err with the format specifier.
// If err is nil, WithMessagef returns nil.
func WithMessagef(err error, format string, args ...interface{}) error {
	if err == nil {
		return nil
	}
	return &withMessage{
		cause: err,
		msg:   fmt.Sprintf(format, args...),
	}
}

type withMessage struct {
	cause error
	msg   string
}

func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } // 实现error 接口
func (w *withMessage) Cause() error  { return w.cause } // 根因

// Unwrap provides compatibility for Go 1.13 error chains.
func (w *withMessage) Unwrap() error { return w.cause } // 拆箱获取根因

func (w *withMessage) Format(s fmt.State, verb rune) {  // 格式化输出
	switch verb {
	case 'v':
		if s.Flag('+') {
			fmt.Fprintf(s, "%+v\n", w.Cause())
			io.WriteString(s, w.msg)
			return
		}
		fallthrough
	case 's', 'q':
		io.WriteString(s, w.Error())
	}
}

errors.Cause(err error) error 获取最底层错误原因(根因)

// Cause returns the underlying cause of the error, if possible.
// An error value has a cause if it implements the following
// interface:
//
//     type causer interface {
//            Cause() error
//     }
//
// If the error does not implement Cause, the original error will
// be returned. If the error is nil, nil will be returned without further
// investigation.
func Cause(err error) error {
	type causer interface {
		Cause() error
	}

	for err != nil {
		cause, ok := err.(causer)
		if !ok {
			break
		}
		err = cause.Cause()
	}
	return err
}

在使用该库时应注意,最底层错误原因打包一次即可,否则会出现多倍相同的堆栈信息,所以应把控err返回值是否直接透传。

截止目前,该博文中标准库源码版本为Go 1.19.3, github.com/pkg/errors的版本为0.9.1。

Go 1.20 对标准库errors的新提案是:
增加errors.Join API
err = errors.Join(err1,err2)
同时修改errors.Unwrap API,让其返回[]error。
fmt.Errorf,errors.Is, errors.As也会做相应修改。
Reference
毛剑老师的《Go进阶训练营》
https://coolshell.cn/articles/21140.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

metabit

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值