【问题标题】:Go - How can I subtype a wrapped error class?Go - 我如何子类型一个包装的错误类?
【发布时间】:2021-11-06 07:32:15
【问题描述】:

我正在包装错误(以添加上下文),然后区分两个错误。这是我目前用于测试的场景。 (函数是否正确识别错误?)我的问题是如何减少冗长。

我有两个函数会产生不同的错误:

func a() error {
    return errors.New("a")
}

func b() error {
    return errors.New("b")
}

它们都由传播错误的第三个函数调用。

func doStuff() error {
    err := a()
    if err != nil {
        return WrapA{err}
    }
    err = b()
    if err != nil {
        return WrapB{err}
    }
    return nil
}

在我的主要功能中,我区分了这两个错误。

func main() {
    fmt.Println("Hello, playground")
    err := doStuff()
    
    switch err.(type) {
        case WrapA:
            fmt.Println("error from doing a")
        case WrapB: 
            fmt.Println("error from doing b")
        case nil:
            fmt.Println("nil")
        default:
            fmt.Println("unknown")
    }
}

到目前为止,一切都很好。不幸的是,要实现WrapAWrapB,我需要很多代码:

type WrapA struct {
    wrappedError error
}

func (e WrapA) Error() string {
    return e.wrappedError.Error()
}

func (e WrapA) Unwrap() error {
    return e.wrappedError
}

type WrapB struct {
    wrappedError error
}

func (e WrapB) Error() string {
    return e.wrappedError.Error()
}

func (e WrapB) Unwrap() error {
    return e.wrappedError
}

在其他语言中,我将创建一个 Wrap 结构并让 WrapAWrapBWrap 继承。但我没有看到在 Go 中执行此操作的方法。

关于如何减少混乱的任何想法?

去游乐场https://play.golang.org/p/ApzHC_miNyV

编辑: 看到 jub0bs 的回答后,我想澄清一下: a()b() 都是我无法控制的回调。他们可能会返回各种错误。这就是我包装它们的原因。

【问题讨论】:

  • go 中没有子类型。没有类层次结构。
  • 当然,我只是想知道我是否错过了一种更 Go-esque 的方法来完成同样的任务。

标签: go types


【解决方案1】:

如果可以,请使用constant errors。然后你可以打开错误本身。

【讨论】:

  • 当您想包装错误消息时,我认为常量不起作用。
【解决方案2】:

如果我对问题的理解正确,您确实可以简化事情:

  • ab 定义为包级别的 error 变量,以便轻松获得更好的性能。
  • 除非您需要以编程方式访问只能在您包装的错误上下文中访问的值,否则您很可能不需要声明这些自定义 WrapAWrapB 错误类型。相反,您可以简单地使用 the %w verb in conjunction with fmt.Errorf 来生成一个新的错误值来包装较低级别的错误。
  • 然后您可以在无标记的switch 中使用errors.Is 来检查doStuff 函数返回的更高级别错误的原因。

(Playground)

package main

import (
    "errors"
    "fmt"
)

var (
    a = errors.New("a")
    b = errors.New("b")
)

func doStuff() error {
    err := a
    if err != nil {
        return fmt.Errorf("%w", err)
    }
    err = b
    if err != nil {
        return fmt.Errorf("%w", err)
    }
    return nil
}

func main() {
    fmt.Println("Hello, playground")
    switch err := doStuff(); {
    case errors.Is(err, a):
        fmt.Println("error from doing a")
    case errors.Is(err, b):
        fmt.Println("error from doing b")
    case err == nil:
        fmt.Println("nil")
    default:
        fmt.Println("unknown")
    }
}

【讨论】:

  • 这里是来自 fmt 包的相关代码,供任何想知道cs.opensource.google/go/go/+/refs/tags/go1.17:src/fmt/…的人参考
  • 这仅适用于 (1) 我可以控制错误在 a() 和 b() 中的定义方式,并且 (2) 两者都只返回一个错误。在我的完整用例中,这两个条件都不满足。我将编辑问题以明确这一点。
  • @magum 最初的问题并不清楚;你的编辑改变了一些事情。澄清一下:您要确定doStuff返回的错误原因是函数a还是函数b,对吗?您对潜在错误的性质不太感兴趣,对吗?
  • 不,我绝对想保留潜在的错误。这就是我包装它的原因。我正在寻找一种不那么冗长的方法来做同样的事情:(1) 保留来自a()b() 的错误信息,(2) 使用上下文"error while calling a(): ..." 丰富错误,以及(3) 匹配原点主要。我看到的唯一其他选项可以在不换行的情况下完成相同的操作,那就是匹配字符串前缀。所以,像strings.HasPrefix(err.Error(), "error while calling a()") 这样的东西。这对我来说似乎不是一个干净的解决方案。
  • @magum 那我想不出办法来减少你方法的冗长¯\_(ツ)_/¯
【解决方案3】:

添加一个结构化的错误版本,该版本构成一个类型 Wrap 以及各种更具体的错误类型;

package main

import (
    "errors"
    "fmt"
)

func a() error {
    return errors.New("something more specific broke in a")
}

func b() error {
    return errors.New("something more specific broke in b")
}

func doStuff() error {
    err := a()
    if err != nil {
        return ErrA{
            Wrap:        Wrap{err: err},
            SpecficProp: "whatever",
        }
    }
    err = b()
    if err != nil {
        return ErrB{
            Wrap:         Wrap{err: err},
            SpecficProp2: "whatever else",
        }
    }
    return nil
}

func main() {
    fmt.Println("Hello, playground")
    err := doStuff()

    if target := (ErrA{}); errors.As(err, &target) {
        fmt.Printf("%v\n", target)
    } else if target := (ErrB{}); errors.As(err, &target) {
        fmt.Printf("%v\n", target)
    } else if err != nil {
        fmt.Println("unknown")
    } else {
        fmt.Println("nil")
    }
}

type Wrap struct {
    err error
}

func (e Wrap) Error() string {
    return e.err.Error()
}

func (e Wrap) Unwrap() error {
    return e.err
}

type ErrA struct {
    Wrap
    SpecficProp interface{}
}

func (e ErrA) Error() string {
    return fmt.Sprintf("got error of kind A with %#v, plus %T", e.SpecficProp, e.Unwrap())
}

type ErrB struct {
    Wrap
    SpecficProp2 interface{}
}

func (e ErrB) Error() string {
    return fmt.Sprintf("got error of kind B with %#v, plus %T", e.SpecficProp2, e.Unwrap())
}

【讨论】:

  • 这里,ErrA 和 ErrB 不是包装错误。因此,如果我们稍后无法匹配包含的错误。否则,我喜欢这个主意。我试图在这里进一步简化:play.golang.org/p/diIDlG-l1zC
  • 避免。如果调用者是并发的,你将有比赛。这就是我使用if var decl; cond 声明的原因。但是是的,这里没有包装这些,但是使用这个模式和 fmt 包的%w 动词,你可以包装任何错误。
猜你喜欢
  • 1970-01-01
  • 2023-03-14
  • 2017-12-27
  • 2019-10-28
  • 2021-05-02
  • 1970-01-01
  • 2014-01-22
  • 2020-12-12
  • 2020-10-07
相关资源
最近更新 更多