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,所以没有分析到。多错误处理,对于多个并行任务出现错误,我要统一收集,还有就是出现错误之后,我将错误发送邮件,邮件发送再出现错误,所有这个链条错误我们也要收集,也会用到多错误,多错误库,目前还是有些的
多错误库的实现还是比较简单的,就是一个错误数组,然后每次操作遍历这个数组,再按单个错误处理,这里就不分析了,感兴趣的可以花几分钟去看看源码