我已经说过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() error
和Unwrap() []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中的错误。