原文地址:Go Errors详解

Golang 中的错误处理和 PHP、JAVA 有很大不同,没有 try...catch 语句来处理错误。因而,Golang 中的错误处理是一个比拟有争议的点,如何更好的 了解解决 错误信息是值得去深入研究的。

Go 内置 errors

Go error 是一个接口类型,它蕴含一个 Error() 办法,返回值为 string。任何实现这个接口的类型都能够作为一个谬误应用,Error 这个办法提供了对谬误的形容:

// http://golang.org/pkg/builtin/#error// error 接口的定义type error interface {    Error() string}// http://golang.org/pkg/errors/error.go// errors 构建 error 对象type errorString struct {    s string}func (e *errorString) Error() string {    return e.s}

error 是一个接口类型,它蕴含一个 Error() 办法,返回值为 string。只有实现这个 interface 都能够作为一个谬误应用,Error 这个办法提供了对谬误的形容。

error 创立

error 的创立形式有两种办法:

1. errors.New()

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

Q:为何 errors.New() 要返回指针?

A:防止 New 的内容相当,造成的歧义,看看上面的例子就能够了解为什么了:

func main() {    errOne := errors.New("test")    errTwo := errors.New("test")    if errOne == errTwo {      log.Printf("Equal \n")    } else {      log.Printf("notEqual \n")    }}

输入:

notEqual

如果应用 errorString 的值去比拟,当我的项目逐步盘大、简单,对于 New() 内容也就难以保障惟一,到那时对于问题的排查,也将是灾难性的。

有些时候咱们须要更加具体的信息。即须要具体的 “上下文” 信息,表明具体的谬误值。

这就用到了 fmt.Errorf 函数

2. fmt.Errorf()

fmtErr := fmt.Errorf("fmt.Errorf() err, http status is %d", 404)fmt.Printf("fmtErr errType:%T,err: %v\n", fmtErr, fmtErr)

输入:

fmtErr errType is *errors.errorString,err is fmt.Errorf() err, http status is 404

为什么 fmtErr 返回的谬误类型也是 :*errors.errorString,咱们不是用 fmt.Errorf() 创立的吗?

一起来看下源码:

// 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 ...interface{}) 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}

通过源码,能够发现,p.wrappedErrnil 的时候,会调用 errors.New() 来创立谬误。

那问题来了,这个 p.wrappedErr 是什么?

咱们来看个例子:

wErrOne := errors.New("this is one ")wErrTwo := fmt.Errorf("this is two %w", wErrOne)fmt.Printf("wErrOne type is %T err is %v \n", wErrOne, wErrOne)fmt.Printf("wErrTwo type is %T err is %v \n", wErrTwo, wErrTwo)

输入:

wErrOne type is *errors.errorString err is this is one  wErrTwo type is *fmt.wrapError err is this is two this is one  

发现没有?应用 %w 返回的 error 对象,输入的类型是 *fmt.wrapError

%w 是 go 1.13 新减少的错误处理个性 。

Go 错误处理实际

如何取得更具体错误信息,比方stack trace,帮忙定位谬误起因?

有人说,层层打 log,但这会造成日志打失去处都是,难以保护。

又有人说,应用 recover 捕捉 panic,然而这样会导致 panic 的滥用。

panic 只用于真正异样的状况,如

  • 在程序启动的时候,如果有强依赖的服务呈现故障时 panic 退出
  • 在程序启动的时候,如果发现有配置显著不符合要求, 能够 panic 退出(进攻编程)
  • 在程序入口处,例如 gin 中间件须要应用 recovery 预防 panic 程序退出

pkg/errors 库

这里,咱们通过一个很小的包 github.com/pkg/errors 来试图解决下面的问题。

看一个案例:

package mainimport (    "github.com/pkg/errors"    "log"    "os")func main() {    err := mid()    if err != nil {      // 返回 err 的根本原因      log.Printf("cause is %+v \n", errors.Cause(err))      // 返回 err 调用的堆栈信息      log.Printf("strace tt %+v \n", err)    }}func mid() (err error) {        return test()}func test() (err error) {        _, err = os.Open("test/test.txt")    if err != nil {          return errors.Wrap(err, "open error")    }    return nil}

输入:

2022/01/17 00:26:17 cause is open test.test: no such file or directory 2022/01/17 00:26:17 strace tt open test.test: no such file or directoryopen errormain.test        /path/err/wrap_t/main.go:41main.mid        /path/err/wrap_t/main.go:35main.main        /path/err/wrap_t/main.go:13runtime.main        /usr/local/Cellar/go/1.17.2/libexec/src/runtime/proc.go:255runtime.goexit        /usr/local/Cellar/go/1.17.2/libexec/src/runtime/asm_amd64.s:1581 

pkg/errors

下层调用者应用errors.Cause(err)办法就能拿到这次谬误造成的罪魁祸首。

// 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(),   }}
// Is 指出以后的谬误链是否存在指标谬误。func Is(err, target error) bool// As 查看以后谬误链上是否存在指标类型。若存在则ok为true,e为类型转换后的后果。若不存在则ok为false,e为空值func As(type E)(err error) (e E, ok bool)

参考

https://go.googlesource.com/p...

https://github.com/golang/go/...

https://github.com/pkg/errors

https://go.googlesource.com/p...

https://go.googlesource.com/p...