避免GO1.20错误处理
#设计 #go #errors

我已经说过Golang Error Handling中的Golang错误处理有多严重。这篇文章着重于GO1.20中错误处理的新添加。

我的热门

我的看法仍然是Golang提供的错误处理不是很好,但是我相信新的GO1.20添加物将问题的全新维度带入了 straggering 的语言。坦率地说,我从未见过通过一种积极使语言更糟的语言发布的功能。这个和其他奇怪的决定使我对Golang的未来非常关注。如果我在代码审查中看到errors.Join()Unwrap() []error,那将无法获得我的批准。

前GO1.20

让我展示一个前GO1.20事物状态的示例,只是为了强调这些新变化的荒谬性。 error是任何实现的链接列表,即消费者未知的任何实现,只需要满足Error() string。让我们看一下如果我们没有一个不错的error单词的脸。

// Go Playground: https://go.dev/play/p/f3Xwyv30C4U
type LinkedList struct {
    value any
    next  *LinkedList
}

func New(value any) *LinkedList {
    return &LinkedList{
        value: value,
        next:  nil,
    }
}

func Wrap(value any, currentList *LinkedList) *LinkedList {
    return &LinkedList{
        value: value,
        next:  currentList,
    }
}

func DoWork() *LinkedList {
    if resultLinkedList := addJob(); resultLinkedList != nil {
        return Wrap("DoWork failed", resultLinkedList)
    }

    return nil
}

这实际上是当您创建错误并将其包裹在Golang中时发生的事情。注意:我省略了链接的列表遍历。如果有人试图用任何语言实现此错误模式,我会非常担心。它很复杂,容易发生且反图案。它还泄漏实施细节。 DoWork()的任何消费者还必须知道addJob()的作用以及其错误返回的含义。否则要说的是忽略链接列表的本质。这本身不是很好,但是GO1.20会变得更糟。

输入GO1.20

此更新向errors软件包介绍了两个新添加:

  • errors.join()
  • unwrap()[]错误

这意味着我们现在可以在包装时指定多个错误。例如:fmt.Errorf("errors: %w, %w", err1, err2)。如果您注意到,这也意味着我们现在必须跟踪多个错误,而不仅仅是每个错误实例,这意味着我们已经从链接列表移至错误树。大小和时间复杂性只是增加了。考虑上面的LinkedList示例,以及如何变得更加复杂。

那么,在GO1.20中,这是什么样子。这是一个小例子。

// Go Playground: https://go.dev/play/p/-iUZt7kSp6-
func externalFn1() error {
    return fmt.Errorf("%w OR %w?", err1, err2)
}

func externalFn2() error {
    return fmt.Errorf("%w OR %w?", err2, err1)
}

func printErr(err error) {
    switch {
    case errors.Is(err, err1):
        fmt.Println("err is err1 - " + err.Error())
    case errors.Is(err, err2):
        fmt.Println("err is err2 - " + err.Error())
    }
}

// printErr(externalFn1())
// prints -> err is err1 - one OR two?
// printErr(externalFn2())
// prints -> err is err1 - two OR one?

这突出了一个巨大的问题:错误是err1和err2。但是,当您检查它们时,您永远不会陷入ERR2案例,因为ERR1首先匹配。

由于一个函数现在可以返回多重错误,因此无法确定实际错误是什么。开发人员应该如何处理这些错误?也许一个函数只有一个错误,但可能性可能不止一个,因此所有错误处理都必须考虑到该情况。也许一个函数返回两个错误,这些错误应结合使用,例如HTTP状态代码和引起的错误,而另一个功能则返回多个错误,但应处理某些错误。不用阅读每个功能并了解其内部行为的文档就无法了解。到那时,您所拥有的不再是错误处理,而是带有业务逻辑,文档和各种用例的完整应用程序。这不是我们从错误处理中需要的。

可能不会立即明显的一个方面是,由于Golang的规则可能不会超载函数,错误类型不能同时实现Unwrap() errorUnwrap() []error。这意味着,如果您有LinkedList错误和树错误的混合,则肯定会遇到麻烦。错误类型只能实现该函数的一个版本,因此现在必须更新Unwrap() error的任何代码以处理两种情况。

要演示此问题,请查看以下示例,并观察到解释错误的结果是nil

// Go Playground: https://go.dev/play/p/0gVlavnmvJf
err := fmt.Errorf("this is bad: %w, %w", err1, err2)
unwrappedErr := errors.Unwrap(err)
fmt.Println(unwrappedErr) // prints <nil>

与同一代码相比,但只包装一个错误。

// Go Playground: https://go.dev/play/p/0gVlavnmvJf
err := fmt.Errorf("this is bad: %w", err1)
unwrappedErr := errors.Unwrap(err)
fmt.Println(unwrappedErr) // prints one

在性能方面,情况变得更糟。我们在这里没有订购错误树,因此在案例搜索较差的情况下,我们仍然有o(n)。另外,困扰LinkedList错误的所有问题仍然困扰着树错误,但现在复杂性增加了。

结论和进一步阅读

简而言之,停止。只是不要这样做。我保证我没有涵盖与树错误有关的所有问题。在其他各种线程like this one中,这种方法有一些很好的反馈。仔细阅读the actual proposal thread此功能阐明了我什至尚未考虑的其他问题,例如线程安全。

对于直接,公正的帖子,this blog post很好地解释了新的机制。

我个人认为,在现在从整个应用程序中删除了error之后,我们最好完全忽略error,以及所有更改GO1.20中的错误。