Errors before For 1.13
最简单的错误检查:
if err != nil {
// sth went wrong
}
有事我们需要对sentinel error
进行检查:
var ErrNotFound = errors.New(" not found")
if err != ErrNotFound {
// sth wasn‘t found
}
实现了error interface
的自定义error struct
,进行断言使用获取更丰富的上下文:
type NotFoundError struct {
Name string
}
func (e *NotFoundError) Error() string {
return e.Name + ": not found"
}
if e, ok := err.(NotFoundError); ok {
// e.Name wasn't found
}
函数在调用栈中添加信息向上传递错误,例如对错误发生时发生的情况的简要描述。
if err != nil {
// 一旦用这种方式wrap了原始错误, 相当于原始错误(root error)丢失了
return fmt.Errorf(" decompress %v: %v", name, err)
}
使用创新错误fmt.Errorf
丢弃原始错误中除文本外的所有内容。正如我们在上面的QueryError
中看到的那样,我们有时可能需要定义一个包含底层错误的新错误类型,并将其保存以供代码检查。这里是QuertError
:
type QueryError struct {
Query string // 具体查询哪个地方报错了
Err error // root error
}
程序可以查看QueryError
值以根据底层错误作出决策。
if e, ok := err.(*QueryError);ok && e.Err == ErrPermission {
// query failed because of a permission problem
}
go1.13为errors
和fmt
标准库包引入了新特性,以简化处理包含其他错误的错误。其中最为重要的点:包含另一个错误的error
可以实现返回底层错误的Unwrap
方法。如果e1.Uwrap()
返回e2
,那么我们说e1
包装e2
,我们可以展开e1
来获取e2
。
按照此约定,我们可以为上面的QueryError
指定一个Unwrap
方法,该方法返回其包含的错误:
func (e *QueryError) Unwrap() error { return e.Err }
go1.13errors
包包含两个用于检查错误的新函数:Is
和As
。
// similar to
// if err == NotFound {...}
if errors.Is(err, ErrNotFound) {
// sth wasn't found
}
// similar to
// if e, ok := err.(*QueryError);ok {...}
var e *QueryError
// Note: *QueryError is the type of error.
if errors.As(err, &e) {
// err is a *QueryError, and e is set to the error's value
}
if errors.Is(err, ErrPermission) {
// err, or some error that it wraps, is a permission problem
}
如前所述,使用fmt.Errorf
向错误添加附加信息。
if err != nil {
return fmt.Errorf(" decompress %v: %v", name, err)
}
在Go1.13中fmt.Errorf
支持新的%w
谓词。
if err != nil {
// 用%w将错误包装起来
// return an error which unwraps to err
return fmt.Errorf(" decompress %v: %w", name, err)
}
// %w内部实现
// 内部类似有个wrapError的结构体
type wrapError struct {
msg string
err error
}
func (e *wrapError) Error() string {
return e.msg
}
func (e *wrapError) Unwrap() error {
return e.err
}
用%w
包装错误可用于errors.Is
以及errors.As
:
// ErrPerssion用%w进行包装
err := fmt.Errorf(" access denied: %w", ErrPerssion)
...
// 包装后可以用errors.Is进行判定
if errors.Is(err, ErrPermission) ...
go1.13 Is实现代码:
func Is(err, target error) bool {
if target == nil {
return err == nil
}
isComparable := reflectlite.TypeOf(target).Comparable()
for {
// 拿到根因错误够先去跟target对象比较,相等返回true
if isComparable && err == target {
return true
}
// 先判定是否实现了Is方法
if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
return true
}
// TODO: consider supporting target.Is(err). This would alow user-definable predicates, but
// also may allow for coping with sloppy APIs, thereby making it easier to get away with them.
// 若没有实现Is则不断取调用Unwrap方法取取到根因错误
if err = Unwrap(err); err == nil {
return false
}
}
}
Is方法也可以扩展:
假设我们知道有一些两个error(两个详细类型)判定是否相等,可以做一些自定义的逻辑:
type Error struct {
Path string
User string
}
// 扩展Is方法
func (e *Error) Is(target error) bool {
t, ok := target.(*Error)
if !ok {
return false
}
return (e.Path == t.Path || t.Path == "") && (e.User == t.User || t.User == "")
}
if errors.Is(err, &Error{User: "someuser"}) {
// err's User failed is "someuser".
}
errors & github.com/pkg/errors
pkg/errors目前兼容了errors的Is和As方法的:
package main
import (
"errors"
"fmt"
xerrors "github.com/pkg/errors"
)
var errMy = errors.New("my")
func main() {
err := test2()
fmt.Printf("main: %+v\n", err)
}
func test0() error {
return xerrors.Wrapf(errMy, "test0 failed") // 包含了堆栈信息
}
func test1() error {
return test0()
}
func test2() error {
return test1()
}
// outPut:
main: my
test0 failed
main.test0
../code/go/test/main.go:17
main.test1
../code/go/test/main.go:21
main.test2
../code/go/test/main.go:25
main.main
../code/go/test/main.go:12
runtime.main
../Go/src/runtime/proc.go:204
runtime.goexit
../Go/src/runtime/asm_amd64.s:1374
References
https://www.infoq.cn/news/2012/11/go-error-handle/
https://golang.org/doc/faq#exceptions
https://www.ardanlabs.com/blog/2014/10/error-handling-in-go-part-i.html
https://www.ardanlabs.com/blog/2014/11/error-handling-in-go-part-ii.html
https://www.ardanlabs.com/blog/2017/05/design-philosophy-on-logging.html
https://medium.com/gett-engineering/error-handling-in-go-53b8a7112d04
https://medium.com/gett-engineering/error-handling-in-go-1-13-5ee6d1e0a55c
https://rauljordan.com/2020/07/06/why-go-error-handling-is-awesome.html
https://morsmachine.dk/error-handling
https://crawshaw.io/blog/xerrors