06 Errors For Go1.13

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为errorsfmt标准库包引入了新特性,以简化处理包含其他错误的错误。其中最为重要的点:包含另一个错误的error可以实现返回底层错误的Unwrap方法。如果e1.Uwrap()返回e2,那么我们说e1包装e2,我们可以展开e1来获取e2

按照此约定,我们可以为上面的QueryError指定一个Unwrap方法,该方法返回其包含的错误:

func (e *QueryError) Unwrap() error { return e.Err }

go1.13errors包包含两个用于检查错误的新函数:IsAs

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值