错误处理

吃猪的蛇 · · 990 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

Error Handing with Golang

Go errors are values.
Naming:Error types end in "Error" and error variables start with "Err" or "err".


error:程序还可以继续运行,错误可以被修复或丢弃

error是个简单的内建接口

type error interface {
    Error() string
}

自定义error范例

// PathError records an error and the operation and file path that caused it.
type PathError struct {
    Op string    // "open", "unlink", etc.
    Path string  // The associated file.
    Err error    // Returned by the system call.
}

func (e *PathError) Error() string {
    return e.Op + " " + e.Path + ": " + e.Err.Error()
}

判断error类型可以用type switch或type assertion

for try := 0; try < 2; try++ {
    file, err = os.Create(filename)
    if err == nil {
        return
    }
    if e, ok := err.(*os.PathError); ok && e.Err == syscall.ENOSPC {
        deleteTempFiles()  // Recover some space.
        continue
    }
    return
}

标准库中error示例

// uintptr is an integer type that is large enough to hold the bit pattern of any pointer.
// type uintptr uintptr // 这个在builtin.go文件中定义的,可以看出它不是个指针类型而是用来存储内存地址的

// syscall_unix.go
type Errno uintptr

func (e Errno) Error() string {
    if 0 <= int(e) && int(e) < len(errors) {
        s := errors[e]
        if s != "" {
            return s
        }
    }
    return "errno " + itoa(int(e))
}

func (e Errno) Is(target error) bool {
    switch target {
    case oserror.ErrPermission:
        return e == EACCES || e == EPERM
    case oserror.ErrExist:
        return e == EEXIST || e == ENOTEMPTY
    case oserror.ErrNotExist:
        return e == ENOENT
    }
    return false
}

// zerrors_darwin_amd64.go
// Errors
const (
    E2BIG           = Errno(0x7)
    EACCES          = Errno(0xd)
    EADDRINUSE      = Errno(0x30)
    ......
)   
// errors.go
var (
    ErrInvalid    = errors.New("invalid argument")
    ErrPermission = errors.New("permission denied")
    ErrExist      = errors.New("file already exists")
    ErrNotExist   = errors.New("file does not exist")
    ErrClosed     = errors.New("file already closed")
)

panic:程序不能继续运行,错误不可忽视和修复

panic是个内建函数,panic后程序转而执行defer,继而终止

常用在init()函数,程序初始化失败时

recover:错误修复

前面说引发panic时,会执行defer stack,那么可以在defer stack中处理错误。如果程序不必要终止,则可以recover错误。当错误被recover住的时候,会停止执行stack中的后续defer

示例:One application of recover is to shut down a failing goroutine inside a server without killing the other executing goroutines.

func server(workChan <-chan *Work) {
    for work := range workChan {
        go safelyDo(work)
    }
}

func safelyDo(work *Work) {
    defer func() {
        if err := recover(); err != nil {
            log.Println("work failed:", err)
        }
    }()
    do(work)
}

当然如果在recover时,recover代码块内逻辑错误还是会导致新的错误,程序会re-panic

Wrap and Unwrap error:与error玩俄罗斯套娃游戏

Unwrap, Is, As 函数在wrap.go文件定义(该文件目前只有这三个函数)

errors.As(err Error, target interface{}) bool 函数是将err这个error`s chain(套娃)的最外层error,赋值给target。如果同类error则赋值成功,不是则失败。当然参数有问题会panic,而参数匹配问题需要好好理解。

准确的说,As函数会试着将err参数赋值给target参数所指的对象。于是如果target是nil那么会panic,而且是target所指的对象,即*target,要是个error类型或者是个interface{}

type errorOne string
func (err *errorOne) Error() string {
    return string(*err)
}

type errorTwo struct {
    two string
    one error
}
func (err *errorTwo) Error() string {
    return err.two + err.one.Error()
}

func main() {
    one := errorOne("111")
    two := errorTwo{two:"222", one:&one}
    var what *errorTwo
    // 注意:下面使用what不加&则会panic,因为what还是个nil指针
    // 即使what不是个nil的errorTwo指针还是会panic,因为实现Error接口的是*errorTwo,不是errorTwo。
    // 所以将个*errorTwo类型当作target,那么*target就是errorTwo;
    // 因此要将**errorTwo当作target,这样*target才是*errorTwo
    fmt.Println(errors.As(&two, &what))
}

errors.Is(err, target error) bool 函数会判断err这个error`s chain(套娃)是否有某层的error是target。

注意:只有实现了Unwrap方法的error才会出现在这个error`s chain里(套娃里),对于没实现Unwrap方法的error会使得这个chain断掉。最简单的方式是使用fmt.Errorf("%w", err)它会返回一个wrap好err的error

func main() {
    one := errorOne("111")
    two := errorTwo{two:"222", one:&one}
    err := fmt.Errorf("%w", &two)
    // flase 因为*errorTwo没有实现Unwrap方法
    fmt.Println(errors.Is(err, &one)) 

    errW1 := fmt.Errorf("wrap1-%w", &one)
    errW2 := fmt.Errorf("wrap1-%w", errW1)
    fmt.Println(errors.Is(errW2, &one)) // true
}

如果要想能够使用到Is方法的便捷,但是又不想使用fmt.Errorf+%w这个返回的“匿名”error,那么可以自己实现Unwrap方法。

准确来讲,也不能说fmt.Errorf返回的的error是匿名的,因为Errorf的源码实现里可以看到,返回的error要么是*wrapError类型,要么是errors.New出来的,而New返回的的是*errorString类型。只不过*wrapError和*errorString都是包外不可见的。

func Unwrap(err error) error:Unwrap returns the result of calling the Unwrap method on err, if err's type contains an Unwrap method returning error. Otherwise, Unwrap returns nil.

func (err *errorTwo) Unwrap() error {
    return err.one
}

func main() {
    one := errorOne("111")
    two := errorTwo{two:"222", one:&one}
    err := fmt.Errorf("%w", &two)
    // 此时会返回true,因为*errorTwo实现了Unwrap方法
    fmt.Println(errors.Is(err, &one))
}

Is的巨坑:Is判断时使用的是“==”,这就使得如果想要判断的target是使用指针接收者实现error接口的,那么只有当套娃中某个error和target完全是同一个instance时,即内存地址一样完全就是一个东西时才返回true。

//  这是Is函数实现
func Is(err, target error) bool {
    if target == nil {
        return err == target
    }

    isComparable := reflectlite.TypeOf(target).Comparable()
    for {
        // 使用 == 判断,使得如果err和target是指针类型那就是判断内存地址了
        if isComparable && err == target { 
            return true
        }
        // 揭开套娃(提取出包含Is函数的匿名接口实现),递归判断
        if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
            return true
        }
        // TODO: consider supporing target.Is(err). This would allow
        // user-definable predicates, but also may allow for coping with sloppy
        // APIs, thereby making it easier to get away with them.
        if err = Unwrap(err); err == nil {
            return false
        }
    }
}

type errorOne struct {
    msg string
}
func (err *errorOne) Error() string {
    return err.msg
}

type errorTwo struct {
    two string
    one error
}
func (err *errorTwo) Error() string {
    return err.two + err.one.Error()
}


func main() {
    errW0 := errorOne{msg: "111"}
    errW1 := fmt.Errorf("wrap1-%w", &errW0)
    errW2 := fmt.Errorf("wrap1-%w", errW1)
    fmt.Println(errors.Is(errW2, &errorOne{"111"}))// false
    fmt.Println(errors.Is(errW2, &errW0)) // true
    fmt.Println(errW0 == errorOne{"111"}) // true
    fmt.Println(&errW0 == &errorOne{"111"}) // false
}

可以通过自己重写Is函数的方式,自己实现 interface{ Is(error) bool } 这个匿名接口。

type errorOne struct {
    msg string
}
func (err *errorOne) Error() string {
    return err.msg
}
func (err *errorOne) Is(target error) bool {
    if target == nil {
        return err == target
    }
    switch target.(type) {
    case *errorOne:
        return err.Error() == target.Error()
    default:
        return false    
    }
}

func main() {
    one := errorOne{"111"}
    two := fmt.Errorf("222%w", &one)
    fmt.Println(errors.Is(two, &one)) // true
    // 下面两行输出,当有上面Is方法的时候都为true,当把上面Is方法注释掉时都为false
    fmt.Println(errors.Is(two, &errorOne{"111"}))
    fmt.Println(errors.Is(&one, &errorOne{"111"}))
}

有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:吃猪的蛇

查看原文:错误处理

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

990 次点击  
加入收藏 微博
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传