go 1.13 errors
以套娃的方式保存错误链。
- 使用
fmt.Errorf
的 %w
error 可以包裹着其他 error。
- 使用
%+v
打印 error 时,带有堆栈信息,精确到函数名与行号。
用法
// 在 err 的链中找到与目标匹配的第一个错误,如果有则返回 true,否则返回 false
As(err error, target interface{}) bool
// 判断两个 error 是否相等
Is(err error, target error) bool
// 返回一个新的 error 对象,即使内容一样也是两个不同的对象
New(text string) error
// 如果传入的 err 对象中有 %w 关键字的格式化类容,则会在返回值中解析出这个原始 error,多层嵌套只返回第一个,否则返回 nil
Unwarp(err error) error
创建
方法 1 fmt.Errorf
err1 := errors.New("new error")
err2 := fmt.Errorf("err2 : [%w]", err1)
err3 := fmt.Errorf("err3 : [%w]", err2)
fmt.Println(err3)
方法 2 自定义 struct
type WarpError struct {
msg string
err error
}
func (e *WarpError) Error() string {
return e.msg
}
func (e *WarpError) Unwrap() string {
return e.err
}
拆开
err1 := errors.New("new error")
err2 := fmt.Errorf("err2 : [%w]", err1)
err3 := fmt.Errorf("err3 : [%w]", err2)
fmt.Println(errors.Unwrap(errors.Unwrap(err3)))
错误判断
errors.Is
以前只有一个错误,现在是错误链表。可能一个 err 会 wrapping 了其它 error,因为不知道嵌套了几次,所以现在要通过 errors.Is 遍历判断。
它递归调用 Unwrap 并判断每一层的 err 是否相等,如果有任何一层 err 和传入的目标错误相等,则返回 true。
err1 := errors.New("new error")
err2 := fmt.Errorf("err2 : [%w]", err1)
err3 := fmt.Errorf("err3 : [%w]", err2)
// 过去写法
// if err3 == os.ErrNotExistF {
// }
fmt.Println(errors.Is(err3, err1)) // true
errors.As
之前没有 wrapping error 的时候,我们要把 error 转为另外一个 error,一般都是使用 type assertion 或者 type switch 其实也就是类型断言。
if perr, ok := err.(*os.PathError); ok {
fmt.Println(perr.Path)
}
但是现在给你返回的 err 可能是已经被嵌套了,甚至好几层了,这种方式就不能用了。
与 Is 的区别是,Is 是严格判断相等,As 判断类型是否相同,并提取第一个符合目标类型的错误,用来统一处理某一类错误。
type ErrorString struct {
s string
}
func (e *ErrorString) Error() string {
return e.s
}
var targetErr *ErrorString
err := fmt.Errorf("new error: [%w]", &ErrorString{s: "target err"})
fmt.Println(errors.As(err, &target)) // true
Error Linter
golangci-lint 有两个跟 error 有关的 wrapcheck 和 go-errorlint。
warpcheck
warpcheck 帮助我们确保不会忘记给 error 添加额外的信息。作者认为 github.com/pkg/errors 会因为调用 runtime.Stack(buf []byte, all bool) int
引起性能问题;打印堆栈只会告诉我们发生错误的位置,并不能说明原因;另外多次包装带堆栈的错误信息,最终会在日志中输入大量的日志。
因此在错误中添加额外的上下文仍然是很好的方案。
- 只要你认为在阅读日志时候,对开发人员有帮助,就可以添加额外上下文。
- 从其它 package 返回的错误,添加上下文可以很方便的确定入口点。
选用的 error 类库还是 golang.org/pkg/errors
func (db *DB) getTansactionByID(tranID string) (Transaction, error) {
sql := `SELECT * FROM transaction WHERE id = $1;`
var t Transaction
if err := db.conn.Get(&t, sql, tranID); err != nil {
return Transaction{}, fmt.Errorf("failed to get transaction with ID %s: %v", tranID, err)
}
return t, nil
}
相关阅读 Introducing Wrapcheck: An error wrapping linter for Go
go-errorlint
go-errorlint 帮我们处理下面的疏忽。
// bad
fmt.Errorf("oh noes: %v", err)
// ^ non-wrapping format verb for fmt.Errorf. Use `%w` to format errors
// good
fmt.Errorf("oh noes: %w", err)
// bad
err == ErrFoo
// ^ comparing with == will fail on wrapped errors. Use errors.Is to check for a specific error
// good
errors.Is(err, ErrFoo)
// bad
myErr, ok := err.(*MyError)
// ^ type assertion on error will fail on wrapped errors. Use errors.As to check for specific errors
// good
var me MyError
ok := errors.As(err, &me)
相关链接