此篇彻底解决error的所有疑问

136 阅读2分钟

1.error到底为啥一直被讨论非议

1.1 没有堆栈信息

1.2 if err!=nil的烦人 调用不优雅

2.error相关的所有方法

2.1 errors包和接口


package errors

type error interface {
    Error() string
}

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

2.2 errro的包装和解包装

wrapError:将原有的error类型包一层,且实现error接口方法,另外还有一个Unwrap方法 为啥要包一层呢?因为数据表达可能不够,比如堆栈信息没有

package main

import (
    "errors"
    "fmt"
)

func f1() error {
    err := f2()
    return fmt.Errorf("f1 error: %w", err) // %s -> %w
}

func f2() error {
    err := f3()
    return fmt.Errorf("f2 error: %w", err) // %s -> %w
}

func f3() error {
    return errors.New("initial error")
}

func main() {
    err := f1()

    fmt.Println(err)

    fmt.Println(errors.Unwrap(err)) // 调用Unwrap
    fmt.Println(errors.Unwrap(errors.Unwrap(err))) // 调用Unwrap
}

// 输出如下:

// f1 error: f2 error: initial error
// f2 error: initial error //拨开一层
// initial error //再拨开一层

如何创建wrapError==>fmt.Errorf w% 原理:

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. == nil {
        err = errors.New(s)
    } else {
        err = &wrapError{s, p.wrappedErr}
    }
    p.free()
    return err
}
func newPrinter() *pp {
   p := ppFree.Get().(*pp)
   p.panicking = false
   p.erroring = false
   p.wrapErrs = false
   p.fmt.init(&p.buf)
   return p
}

//pp is used to store a printer's state and is reused with sync.Pool to avoid allocations.

2.3 errors.Is(AError,BError),以及errors.As(AError,&otherError)

Is的判断分为两种情况,第一种:AError自己实现了 Is(error) bool 这个接口,那么它调用这个方法即可. 第二种情况:就是A包装了B这样的情况,如何判断就是递归Unwrap

if errors.Is(err, fs.ErrExist) {
  // todo
}
 
var perr *fs.PathError

if errors.As(err, &perr) {

  fmt.Println(perr.Path)
  
}


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

 }

 

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

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) {// targetType实现了errorType
        panic("errors: *target must be interface or implement error")
    }

    for err != nil {
        if reflectlite.TypeOf(err).AssignableTo(targetType) { //确定当前类型err是否可分配给指定 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
}

如何调用其他的库,写自己的方法

既然error属于golang的异常必选题,那么我们的每个方法是否应该有一个error返回值值呢?

func m1() (int, error) {
	time.Sleep(1 * time.Microsecond)
	return 1, errors.New("error1")
}
func m2() (int, error) {
	time.Sleep(2 * time.Microsecond)
	return 2, errors.New("error2")
}
func test() (string, error) {
	v1, err1 := m1()
	if err1 != nil {
		return "", err1
	}
	v2, err2 := m2()
	if err2 != nil {
		return "", err2
	}
	return strconv.Itoa(v1 + v2), nil
}

func test3() (string, error) {
	v1, err1 := m1()
	if err1 == nil {
		v2, err2 := m2()
		return strconv.Itoa(v1 + v2), err2
	}
	return "v1", err1
}

func test2() (string, error) {
	var g errgroup.Group
	c := make(chan int, 2)
	g.Go(func() error {
		v1, err1 := m1()
		c <- v1
		return err1
	})
	g.Go(func() error {
		v2, err2 := m2()
		c <- v2
		return err2
	})
	var err error
	if err = g.Wait(); err == nil {
		fmt.Println("Successfully fetched all URLs.")
		return "", nil
	}
	result := strconv.Itoa(<-c + <-c)
	return result, err
}