如何在Go语言中处理多个错误?

4
什么是处理Go语言中多个错误的最习惯方式?
我应该尝试将错误进行封装并返回两个吗?
if err := foo(bar, obj); err != nil {
    // how to do I avoid losing this error?
    err := l.fixup(obj)
    if err != nil {
        //  but this error is the not the one from foo?
    }
    return err
}
return l.fixup(obj)

你的第一行代码是一个示例,可以将其放在新的块作用域中。你没有这样做的原因吗? - JimB
存在 foo 的依赖关系。如果 foo 失败,则 fixup 需要注意到这一点。在 foo 之后必须调用 fixup,但基于 foo 的结果它具有不同的行为。实际上,我想看到来自 foo 的错误信息,因为那表明了问题,fixup 不应该失败。 - daxmc99
1
那么为什么不给它们取一个不同的名字呢?显而易见的解决方案通常是可接受的。 - JimB
如果您需要返回多个错误,请参考未来(Go 1.2x,2023年)中的错误切片/树 - VonC
5个回答

4
你可以使用 Dave Cheney 的一个优秀包中的 Wrap 函数向原始错误添加上下文。该函数名为 errors.Wrap,您可以在以下链接中找到这个包:https://github.com/pkg/errors
errors.Wrap 函数返回一个新的错误,将上下文添加到原始错误中。
func Wrap(cause error, message string) error

在您的情况下,这将是:
if cause := foo(bar, obj); cause != nil {
    err := l.fixup(obj)
    if err != nil {
        return errors.Wrap(cause, err.Error())
    }
    return cause
}
return l.fixup(obj)

谢谢!这就是我决定采用的。 - daxmc99
如果错误是相关的,那么包装是有意义的。比如说我有一个函数,它执行一堆验证然后执行一个操作,我应该返回一个(错误列表,结果)还是包装并返回(错误,结果)? - Abdul Kader Jeelani

1
如果必须链接错误并返回,这取决于您的错误意味着什么以及您想通知调用者的哪个错误。通常,当错误的发生不应该停止路径并且调用跟随时,例如在此处foo然后fixup,您将记录第一个错误并返回第二个错误,因为它可能与函数的功能最相关。
还有一些包可以包装错误,以便您可以从多个错误构建错误。
有标准包fmt.Errorf,您可以组合多个错误。
还有https://github.com/hashicorp/go-multierror,允许您在一个错误中保留多个错误。
在您的情况下,如果您想要将两个错误消息上升到更高层次,我会做这样的事情:
err := foo(bar, obj)

if fixupErr := l.fixup(obj); fixupErr != nil {
    if err != nil {
        return fmt.Errorf("foo err: %s\nfixup err: %s\n", err, fixupErr)
    }
    return fixupErr
}
return err

0

fixup 方法在问题的两个代码路径中都被调用。通过在 if 语句外部调用 fixup 来简化代码。

如果你希望来自 foo 的错误优先于来自 fixup 的错误,则只需这样做。

err1 := foo(bar, obj)
err2 := l.fixup(obj)
if err1 != nil {
   return err1
} 
return err2

0

在 Go 1.20 之后,根据发布说明中的Wrapping multiple errors

新函数 errors.Join 返回一个包装了错误列表的错误

原始提案是 errors: 添加对多个错误的支持


示例代码

    if cause := foo(bar, obj); cause != nil {
        err := fixup(obj)
        if err != nil {
            return errors.Join(cause, err)
        }
        return cause
    }
    return fixup(obj)

而且你可以使用errors.Is或者errors.As来检查单个错误

var (
    ErrFixup = errors.New("err fixup")
    ErrFoo   = errors.New("err foo")
)

func foo(bar, obj string) error {
    if bar != "" && obj != "" {
        return ErrFoo
    }
    return nil
}

func fixup(obj string) error {
    if obj != "" {
        return ErrFixup
    }
    return nil
}

func test() error {
    bar := "bar"
    obj := "obj"
    if cause := foo(bar, obj); cause != nil {
        err := fixup(obj)
        if err != nil {
            return errors.Join(cause, err)
        }
        return cause
    }
    return fixup(obj)
}

func main() {
    err := test()
    if errors.Is(err, ErrFixup) {
        fmt.Println("err is fixup")
    }
    if errors.Is(err, ErrFoo) {
        fmt.Println("err is foo")
    }
}

游乐场


0

无论如何,您的代码都会调用l.fixup(obj)。如果foo(bar, obj)返回错误,则会进行一些处理并调用l.fixup(obj) - 否则仅调用l.fixup(obj)。因此,您的代码可以重新排列如下:

// err will only be valid within the if-then-else-construct
if err := foo(bar, obj); err != nil {
    // handle error from foo(bar,obj)
    // you can even return it, if you wanted to
    // For the sake of this example, we simply log it
    log.Println("Executing foo: %s", err)
}
return l.fixup(obj)

此外,如果您想区分由fool.fixup返回的错误,您可以利用error是一个接口的事实。您可以通过为其中一个(或两个)创建一个类型化的错误,并使用所谓的类型开关来评估错误的类型来实现这一点。
package main

import (
    "errors"
    "fmt"
)

// FooError is the error to be returned by foo
type FooError struct {
    Bar string
}

// Error implements the interface
func (f FooError) Error() string {
    return fmt.Sprintf("%s: interface is nil", f.Bar)
}

// dummy foo func
func foo(bar string, in interface{}) error {
    if in == nil {
        return FooError{Bar: bar}
    }
    return nil
}

// dummy fixup func
func fixup(in interface{}) error {
    if in == nil {
        return errors.New("Interface is nil")
    }
    return nil
}

// a wrapper, containing a variation of above code
func wrap(bar string) error {
    if err := foo(bar, nil); err != nil {
        // handle error from foo(bar,obj)
        // you can even return it, if you wanted to
        return err
    }
    return fixup(nil)
}

func main() {
    err := wrap("test")

    // The type switch itself
    switch err.(type) {
    case FooError:
        // We have a FooError, so we can deal with it accordingly
        fmt.Println("Foo Error:",err)
    default:
        // Every other error is handled by the default block
        fmt.Println("Std Error:",err)
    }
}

然而,这种做法并不太合适。如果调用foo并且返回错误会阻止你的逻辑中的其他操作未能执行,那么抛出异常可能是有效的选择。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接