如何将 golang 错误包装成不透明错误?

How can I wrap a golang error into an opaque error?

如何将错误包装成不透明错误(如 Dave Cheney 在 https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully 中所述)?另外,我希望不透明的错误有一个堆栈跟踪,并通过 return 链保留它。

errors.Wrap() 使用堆栈跟踪创建一个新错误,但不是我的不透明类型。我该怎么做(添加堆栈跟踪并将其设为 MyErr,临时设为 true)?

package main

import (
    "fmt"
    "github.com/pkg/errors"
)

type temporary interface {
    Temporary() bool
}

func IsTemporary(err error) bool {
    te, ok := err.(temporary)
    return ok && te.Temporary()
}

type MyError struct {
    error
    isTemporary bool
}

func (e MyError) Temporary() bool {
    return e.isTemporary
}

func f1() error {   // imitate a function from another package, that produces an error
    return fmt.Errorf("f1 error")
}

func f2() error {
    err := f1()
    myErr := errors.Wrap(err, "f2 error")   // Wrap() adds the stacktrace
    // how to wrap it as a temporary MyErr?
    return myErr
}

func f3() error {
    err := f2()
    return fmt.Errorf("f3 error: %+v", err) // don't Wrap() here or we get another stacktrace
}

func f4() error {
    err := f3()
    return fmt.Errorf("f4 error: %+v", err) // the '+' isn't needed here but does no harm
}

func main() {
    err := f4()
    if err != nil {
        if IsTemporary(err) {
            fmt.Println("temporary error")
        }
        fmt.Printf("oops: %+v\n", err)
    }
}

这将打印以下内容:

oops: f4 error: f3 error: f1 error
f2 error
main.f2
        /home/jlearman/projects/axon-internal/ibm/pocs/ibm-cloud/vmware-vms/err2.go:32
main.f3
        /home/jlearman/projects/axon-internal/ibm/pocs/ibm-cloud/vmware-vms/err2.go:38
main.f4
        /home/jlearman/projects/axon-internal/ibm/pocs/ibm-cloud/vmware-vms/err2.go:43
main.main
        /home/jlearman/projects/axon-internal/ibm/pocs/ibm-cloud/vmware-vms/err2.go:48
runtime.main
        /usr/local/go/src/runtime/proc.go:255
runtime.goexit
        /usr/local/go/src/runtime/asm_amd64.s:1581

没错,只是我想先看到“临时错误”。

假设 f1 实际上在第 3 方或内置代码中,return 是标准 error 类型。 f2 是我的代码中收到该错误的第一个函数,需要在适当的时候将其设为临时函数。 (如果它最初是临时的,那将是一个后续问题,但我想我可以弄清楚。)

我希望从我们的代码中提取的错误处理模式 return 在整个项目中保持一致,这将相对较大。

您不能使用 github.com/pkg/errors 函数真正做到这一点。这是因为用于包装的错误类型是未导出的,因此您不能将其嵌入到您自己的自定义错误中。

然而,由于您不反对使用 stdlib errors 包以外的错误库,这里是您如何使用 juju errors 包(因为它的 Err 类型被导出):

package main

import (
    "fmt"

    "github.com/juju/errors"
)

type temporary interface {
    Temporary() bool
}

func IsTemporary(err error) bool {
    for {
        te, ok := err.(temporary)
        if ok {
            return te.Temporary()
        }

        er, ok := err.(*errors.Err)
        if ok {
            err = er.Underlying()
            continue
        }

        return false
    }
}

type MyError struct {
    errors.Err
    isTemporary bool
}

func (e MyError) Temporary() bool {
    return e.isTemporary
}

func f1() error { // imitate a function from another package, that produces an error
    return errors.Errorf("f1 error")
}

func f2() error {
    err := f1()
    wrappedErr := errors.Annotate(err, "f2 error")
    return &MyError{
        Err:         *wrappedErr.(*errors.Err),
        isTemporary: true,
    }
}

func f3() error {
    err := f2()
    return errors.Annotate(err, "f3 error")
}

func f4() error {
    err := f3()
    return errors.Annotate(err, "f4 error")
}

func main() {
    err := f4()
    if err != nil {
        if IsTemporary(err) {
            fmt.Println("temporary error")
        }
        if e, ok := err.(*errors.Err); ok {
            fmt.Printf("oops: %+v\n", e.StackTrace())
        }
    }
}