共计 3909 个字符,预计需要花费 10 分钟才能阅读完成。
原文地址: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.wrappedErr
为 nil
的时候,会调用 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 main | |
import ( | |
"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 directory | |
open error | |
main.test | |
/path/err/wrap_t/main.go:41 | |
main.mid | |
/path/err/wrap_t/main.go:35 | |
main.main | |
/path/err/wrap_t/main.go:13 | |
runtime.main | |
/usr/local/Cellar/go/1.17.2/libexec/src/runtime/proc.go:255 | |
runtime.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…