go的error的分析

        error处理对于我们写程序来说是一个很重要的东西,比如http,grpc的error,他们是errcode和errmsg的解析,而且内部的errcode的常量数字已经写死了,某个常量code对于具体的msg,感兴趣的可以去看看errcode的代码,但是对于error的产生,他有很多种方法,比如我们看到的

errN:=errors.New("error") //1
errS := fmt.Errorf("error is s format %s", "s") //2
fmt.Errorf("error is w format %w", errS) //3
fmt.Errorf("error is v format %v", errS) //4

当然也有自定义struct将error包裹进去的,对于这样error的分析,我们只要知道它的原理,所有的东西就会迎刃而解,对于程序员而说,只要有源码在手,没有分析不出的问题。

1.error的产生

        对于go的error,它的接口是

type error interface {
    Error() string
}

        而对于第一种情况

errN := errors.New("error")

//自定义的struct的string,实现了error的接口
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
}

它是一个struct,实现了error的接口

        对于2,3,4种情况,我们一起分析,因为有个函数是fmt包下的print函数,这个分析也会顺带一起分析go的打印函数

func Errorf(format string, a ...any) error {
	p := newPrinter()
	p.wrapErrs = true
	p.doPrintf(format, a)  //这个是print的打印函数相当的重要
	s := string(p.buf)
	var err error
	if p.wrappedErr == nil {
        //2和4走的这里
		err = errors.New(s)
	} else {
        //3走的这里
		err = &wrapError{s, p.wrappedErr}
	}
	p.free()
	return err
}

上面的doPrintf函数是go的print函数,其中会返回p.wrappedErr是否为空,只有%w的形式,才会不会空,所以%w返回的是下面的地址

type wrapError struct {
	msg string
	err error
}

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

func (e *wrapError) Unwrap() error {
	return e.err
}

而%s,%v返回的是&errorString,和errors.new是一样的。比如fmt.Errorf("error is w format %w", errS),它的msg是error is w format error is s format s,err是errS,而fmt.Errorf("error is v format %v", errS)只有errorString 的s是error is v format error is s format s,下面我们就来具体分析产生的原因doPrintf函数

//对于这样的print函数而言,都是状态机来分析出每个符号对应的标志位,然后具体操作

比如format ="this is %d this is %f" a=[]any{23,12.4}

func (p *pp) doPrintf(format string, a []any) {
	end := len(format)
	argNum := 0         // we process one argument per non-trivial format
	afterIndex := false // previous item in format was an index like [3].
	p.reordered = false
formatLoop:
	for i := 0; i < end; {
		p.goodArgNum = true
		lasti := i
        //直到找到format的%的位置
		for i < end && format[i] != '%' {
			i++
		}
		if i > lasti {
			p.buf.writeString(format[lasti:i])
		}
		if i >= end {
			// done processing format string
			break
		}

		// Process one verb
		i++

		// Do we have flags?
		p.fmt.clearflags()
	simpleFormat:
        //找到%后紧挨着的符号,然后根据符号设置具体的标志位,后面解析这个标志位
		for ; i < end; i++ {
			c := format[i]
			switch c {
			case '#':
				p.fmt.sharp = true
			case '0': //比如%0f
				p.fmt.zero = !p.fmt.minus // Only allow zero padding to the left.
			case '+': //比如%+v
				p.fmt.plus = true
			case '-':
				p.fmt.minus = true
				p.fmt.zero = false // Do not pad with zeros to the right.
			case ' ':
				p.fmt.space = true
			default:
				// Fast path for common case of ascii lower case simple verbs
				// without precision or width or argument indices.
				if 'a' <= c && c <= 'z' && argNum < len(a) {//这里就是%s,%w,%v,%f,都走这里
					if c == 'v' {//如果是%v
						// Go syntax
						p.fmt.sharpV = p.fmt.sharp
						p.fmt.sharp = false
						// Struct-field syntax
						p.fmt.plusV = p.fmt.plus
						p.fmt.plus = false
					}
                    //这里就是一个%号后面的标志位,跟着一个参数解析,以上面的例子就是
                   // p.printArg(23, d)//d代表assci码,这是第一次参数
                    //p.printArg(12.4, f)//d代表assci码,这是第二次参数
                    //对于2,3,4的error来说
                    //p.printArg("s", s) 
                    //p.printArg(errS, w) 
                    //p.printArg(errS, v) 

					p.printArg(a[argNum], rune(c))
					argNum++
					i++
					continue formatLoop
				}
				// Format is more complex than simple flags and a verb or is malformed.
				break simpleFormat
			}
		}


        .
        .
        .

 从上面的分析可知道,doPrintf函数根据状态机来分析每个%后字符的系列化,然后处理单独的序列化p.printArg(a[argNum], rune(c)),下面来分析printArg序列化函数

//根据具体的参数要分析序列化的分类
func (p *pp) printArg(arg any, verb rune) {
	p.arg = arg
	p.value = reflect.Value{}

	if arg == nil {
		switch verb {
		case 'T', 'v':
			p.fmt.padString(nilAngleString)
		default:
			p.badVerb(verb)
		}
		return
	}

	// Special processing considerations.
	// %T (the value's type) and %p (its address) are special; we always do them first.
	switch verb { // verb 是T和p的序列化
	case 'T':
		p.fmt.fmtS(reflect.TypeOf(arg).String())
		return
	case 'p':
		p.fmtPointer(reflect.ValueOf(arg), 'p')
		return
	}

	// Some types can be done without reflection.
	switch f := arg.(type) { //对应arg参数类型的序列化
	case bool:
		p.fmtBool(f, verb)
	case float32:
		p.fmtFloat(float64(f), 32, verb)
	case float64:
		p.fmtFloat(f, 64, verb)
	case complex64:
		p.fmtComplex(complex128(f), 64, verb)
	case complex128:
		p.fmtComplex(f, 128, verb)
	case int:
		p.fmtInteger(uint64(f), signed, verb)
	case int8:
		p.fmtInteger(uint64(f), signed, verb)
	case int16:
		p.fmtInteger(uint64(f), signed, verb)
	case int32:
		p.fmtInteger(uint64(f), signed, verb)
	case int64:
		p.fmtInteger(uint64(f), signed, verb)
	case uint:
		p.fmtInteger(uint64(f), unsigned, verb)
	case uint8:
		p.fmtInteger(uint64(f), unsigned, verb)
	case uint16:
		p.fmtInteger(uint64(f), unsigned, verb)
	case uint32:
		p.fmtInteger(uint64(f), unsigned, verb)
	case uint64:
		p.fmtInteger(f, unsigned, verb)
	case uintptr:
		p.fmtInteger(uint64(f), unsigned, verb)
	case string: //很明显2的arg是"s",所以走的这个分支
		p.fmtString(f, verb)
	case []byte:
		p.fmtBytes(f, verb, "[]byte")
	case reflect.Value:
		// Handle extractable values with special methods
		// since printValue does not handle them at depth 0.
		if f.IsValid() && f.CanInterface() {
			p.arg = f.Interface()
			if p.handleMethods(verb) {
				return
			}
		}
		p.printValue(f, verb, 0)
	default://而errosS走的是这里的分支,因为它是一个指针,所以3和4走这里
		// If the type is not simple, it might have methods.
		if !p.handleMethods(verb) {
			// Need to use reflection, since the type had no
			// interface methods that could be used for formatting.
			p.printValue(reflect.ValueOf(f), verb, 0)
		}
	}
}

printArg根据args的类型和verd的p和T来直接处理,但对于args是指针,verb不是p和T则继续走下面的函数handleMethods

func (p *pp) handleMethods(verb rune) (handled bool) {
	if p.erroring {
		return
	}

    //第三种情况带%w的,如果args是error接口的,会将p.wrappedErr = err
    //并将verb = 'v',那后面就是走%v同样的逻辑了,所以这里就很清楚了,
    //%w的error返回了p.wrappedErr = err,不是nil了
	if verb == 'w' { 
		// It is invalid to use %w other than with Errorf, more than once,
		// or with a non-error arg.
		err, ok := p.arg.(error)
		if !ok || !p.wrapErrs || p.wrappedErr != nil {
			p.wrappedErr = nil
			p.wrapErrs = false
			p.badVerb(verb)
			return true
		}
		p.wrappedErr = err
		// If the arg is a Formatter, pass 'v' as the verb to it.
		verb = 'v'
	}

    //是否是有Formatter接口,就执行它的接口函数
	// Is it a Formatter?
	if formatter, ok := p.arg.(Formatter); ok {
		handled = true
		defer p.catchPanic(p.arg, verb, "Format")
		formatter.Format(p, verb)
		return
	}

	// If we're doing Go syntax and the argument knows how to supply it, take care of it now.
	if p.fmt.sharpV { //对于%v,且有GoStringer接口的执行GoString接口函数
		if stringer, ok := p.arg.(GoStringer); ok {
			handled = true
			defer p.catchPanic(p.arg, verb, "GoString")
			// Print the result of GoString unadorned.
			p.fmt.fmtS(stringer.GoString())
			return
		}
	} else {
		// If a string is acceptable according to the format, see if
		// the value satisfies one of the string-valued interfaces.
		// Println etc. set verb to %v, which is "stringable".
		switch verb {
		case 'v', 's', 'x', 'X', 'q':
			// Is it an error or Stringer?
			// The duplication in the bodies is necessary:
			// setting handled and deferring catchPanic
			// must happen before calling the method.
			switch v := p.arg.(type) {
			case error: //最后3,4都执行这里,将error的Error()函数拼接进去
				handled = true
				defer p.catchPanic(p.arg, verb, "Error")
				p.fmtString(v.Error(), verb)
				return

			case Stringer: //这个是String(),就是我们实现这个Stringer,就会打印函数
				handled = true
				defer p.catchPanic(p.arg, verb, "String")
				p.fmtString(v.String(), verb)
				return
			}
		}
	}
	return false
}

        对此上面的error的产生解析就很清楚了,除此之外,我们要记住几个接口,对于自定义的struct,如果我们实现Formatter接口,这里会先调用,对于%v和实现了GoStringer会调用这个接口,对于'v', 's', 'x', 'X', 'q'后面的这些符号如果实现了Strnger则会调用这个接口,这个接口很实用,如果我们想自定义的打印数据,就实现这个接口,这个接口用的很多,很多都出现这个接口,记住,这个就是自定义打印。

        printArg函数就是根据args的类型的type和是否实现接口,verb的(s,v,w,p,P,d,..)符号来进行具体的序列化的,到此不仅error的产生分析清楚了,print的函数也分析了一大半了。

2.error的比较和作用

        对于上面的产生error的&errorString和&wrapError到底有什么不同了,为什么要区别了,其实对于wrapError的错误里,可以包裹了原始的error,它可以包裹几层,将原始的error带出来,比较的时候

//Is函数是两个error进行比较
func Is(err, target error) bool {
	if target == nil {
		return err == target
	}

	isComparable := reflectlite.TypeOf(target).Comparable() //是可以比较的
	for { //递归进行比较
		if isComparable && err == target {  //如果相等则返回成功
			return true
		}
        //如果哪一层级的error实现了Is,则调用Is进行比较
		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.
        //递归,将下一层级的err,通过Unwrap来获取,error要实现这个接口,返回包裹的error
        //所以wrapError就实现了这个接口,用这个函数进行递归比较
		if err = Unwrap(err); err == nil { //
			return false
		}
	}
}

所以%w的error用Is函数来看是否包含了某种error。同理我们来看一下As函数,它是提取指定类型的错误,判断包装的 error 链中,某一个 error 的类型是否与 target 相同,并提取第一个符合目标类型的错误的值,将其赋值给 target。

func As(err error, target any) bool {
	if target == nil {
		panic("errors: target cannot be nil")
	}
	val := reflectlite.ValueOf(target)
	typ := val.Type()
    //target必须是指针,因为target要用value赋值
	if typ.Kind() != reflectlite.Ptr || val.IsNil() { 
		panic("errors: target must be a non-nil pointer")
	}
	targetType := typ.Elem()    
    //target如果不是接口,要实现error的接口函数
	if targetType.Kind() != reflectlite.Interface && !targetType.Implements(errorType) {
		panic("errors: *target must be interface or implement error")
	}
	for err != nil {
        //递归的error能赋值给target,就赋值,并返回true
		if reflectlite.TypeOf(err).AssignableTo(targetType) {
			val.Elem().Set(reflectlite.ValueOf(err))
			return true
		}
        //如果某级的error存在当前接口,就调用
		if x, ok := err.(interface{ As(any) bool }); ok && x.As(target) {
			return true
		}
        //递归获取下一级err
		err = Unwrap(err)
	}
	return false
}

        error的as函数target是any,并且有赋值第一个能赋值的,然后返回true。

3.github.com/pkg/errors

        对于上面的error,大家是不是觉得已经掌握error了,但是对于实际应用来说,比如我们将error,不断地像上传递,然后统一打印log,那时候打印log的行号是不是都是一样,最原始的error的行号和堆栈并没有显示出来,这个包下的pkg/errors就会包裹堆栈,让我们分析一下,然后看看

type RetError struct {
	err string
}

func (t RetError) Error() string {
	return fmt.Sprintf("error is return to: %s", t.err)
}

//func (t RetError) Unwrap() error {
//	return t
//}

func GeneratePkgErr() error {
	innerErr := fmt.Errorf("error is %s", "inner")
	//retErr := fmt.Errorf("error is return to  %s", "client")
	return PkgErr.Wrapf(RetError{"client"}, "pkg err:%v", innerErr)
}

func GenerateSysErr() error {
	return fmt.Errorf("sys err:%w", RetError{"client"})
}

func TestError(t *testing.T) {
	geErr := GeneratePkgErr()
	fmt.Printf("%+v\n", geErr)

	retPkg := &RetError{}
	if errors.As(PkgErr.Cause(geErr), retPkg) {
		fmt.Printf("geErr orgin err is RetError:%v\n", retPkg)
	}

	sysErr := GenerateSysErr()
	retSys := &RetError{}
	fmt.Printf("%+v\n", sysErr)
	if errors.As(sysErr, retSys) {
		fmt.Printf("sysErr orgin err is RetError:%v\n", retSys)
	}
}

//打印结果
error is return to: client
pkg err:error is inner
awesomeProject_test.GeneratePkgErr
	D:/学习/qq文件/awesomeProject/mypainc_test.go:26
awesomeProject_test.TestPainc
	D:/学习/qq文件/awesomeProject/mypainc_test.go:33
testing.tRunner
	D:/golang/src/testing/testing.go:1446
runtime.goexit
	D:/golang/src/runtime/asm_amd64.s:1594
geErr orgin err is RetError:error is return to: client
sys err:error is return to: client
sysErr orgin err is RetError:error is return to: client

        对于pkg/error的分析,我们从源码分析

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

//对于返回的withStack-》withMessage->RetError,那我们看打印代码
//fmt.Printf("%+v\n", geErr)
//对于前面的分析fmt.Printf-》doPrintf-》printArg-》handleMethods-》format接口-》func (w *withStack) Format(s fmt.State, verb rune) {
	switch verb {
	case 'v':
		if s.Flag('+') { //对应上面的+v,
			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())
	}
}

上面调用-》fmt.Fprintf(s, "%+v", w.Cause()) -》doPrintf-》printArg-》handleMethods-》format接口-》func (w *withMessage) Format(s fmt.State, verb rune) {
	switch verb {
	case 'v':
		if s.Flag('+') { //对应的+v
            //这里的w.Cause()就是原始的RetError
			fmt.Fprintf(s, "%+v\n", w.Cause())  
            //这里的是fmt.Sprintf(format, args...)
			io.WriteString(s, w.msg) 
			return
		}
		fallthrough
	case 's', 'q':
		io.WriteString(s, w.Error())
	}
}


//+v所以通过流程会先打印RetError.Err,然后是"pkg err:%v", innerErr,
//最后打印orgin的错误RetError的stack

//如果是%s等,就是w.msg + ": " + w.cause.Error(),将错误加起来

//errors.As(PkgErr.Cause(geErr), retPkg)其中的PkgErr.Cause(geErr)它会不断递归调用完
//最后一个没有Cause接口的,这里的递归路径是withStack-》withMessage->RetError,
//因为withStack-》withMessage都实现了这个接口,找到最原始的RetError。

//errors.As(sysErr, retSys) 是通过接口Unwrap递归找到第一个满足能赋值的error


//切忌包裹的不能是指针,如果
func GenerateSysErr() error {
	return fmt.Errorf("sys err:%w", &RetError{"client"})
}

if errors.As(sysErr, retSys) {//这里为false,因为指针不能赋值给当前类型
		fmt.Printf("sysErr orgin err is RetError:%v\n", retSys)
}


        通过上面的分析,我们知道打印函数format接口的应用,和这个错误包裹了一个堆栈,我们能打印出堆栈。

4.multierr

        对于多错误处理,看到1.20已经搞到库里了,我目前是1.19,所以没有分析到。多错误处理,对于多个并行任务出现错误,我要统一收集,还有就是出现错误之后,我将错误发送邮件,邮件发送再出现错误,所有这个链条错误我们也要收集,也会用到多错误,多错误库,目前还是有些的

多错误库的实现还是比较简单的,就是一个错误数组,然后每次操作遍历这个数组,再按单个错误处理,这里就不分析了,感兴趣的可以花几分钟去看看源码

  • 22
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值