Golang错误处理 - pkg/errors中的Cause()是否应更名为RootCause()?

Golang错误处理 - pkg/errors中的Cause()是否应更名为RootCause()?

你好,

刚开始适应`errors`包的使用。
同时,我已经阅读了一些博客,**了解`%+v`选项**,但我希望能够自己遍历错误跟踪信息,而不仅仅是打印出来。

让我们先看代码,然后讨论我遇到的问题。

```go
e = errors.New("[Area] multiplier wasnt a positive int")

e = errors.Wrap(e, "[main] Area failed for -1")
e = errors.Wrap(e, "Top most error")

fmt.Printf("\nErr=%s", fmt.Errorf("%+v", e)) //原始打印:很酷!

for tabs:=0; ; tabs++ {	//我本想用e!=nil,但e永远不会为nil,因为Cause总是返回根因		
    fmt.Printf("\n%s+-- %s", strings.Repeat("    ", tabs), errors.Errorf("%v", e))
    if _e:=errors.Cause(e); _e==e {  // 叶子错误的Cause是错误本身??!!嗯...好吧!
        break; 
    } else {
        e = _e
    }
}

输出:

Err=Top most error: [main] Area failed for -1: [Area] multiplier wasnt a positive int
+-- Top most error: [main] Area failed for -1: [Area] multiplier wasnt a positive int
. +-- [Area] multiplier wasnt a positive int

问题

  1. 当我获取错误时,在堆栈顶部我只想要该级别的消息,而不是包含所有嵌套的错误信息。我该如何实现?
  2. Cause()实际上表现得像RootCause();我如何获取错误的直接嵌套原因?
  3. 我查看了一下,似乎这是人们使用的标准方法。如果我没有遗漏什么的话,我愿意接受任何能够开箱即用处理这些情况的库。

更多关于Golang错误处理 - pkg/errors中的Cause()是否应更名为RootCause()?的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

这确实是 pkg/errors 的工作方式——Cause() 会返回"最初"或"最深层"的错误,如果你愿意的话可以称之为 RootCause()。这通常是与 io.EOF 等进行比较时最值得关注的部分。

可能存在一些用于构建错误树并检查所有层次的包,但这似乎有点过于关注错误的内部细节了……

更多关于Golang错误处理 - pkg/errors中的Cause()是否应更名为RootCause()?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


谢谢。我明白了,有点令人惊讶的是像错误处理这样的问题在标准库中还没有得到很好的解决。

为此,我希望以下内容能够进入标准库:

  • 改进命名规范以避免此类误解
  • 明确区分 CauseRootCause,因为目前以 Cause 形式存在的 RootCause 有着广泛的使用基础
  • 任何库都应该能够提供数据的真实来源,而如何查看它是编程选择。提供默认实现以便通过 %+v 轻松减少冗长(很棒)是很好的,但我希望标准库能够解决这个不足(在我看来)。

我不会引用其他语言的成熟例子来引发争论,但我认为有经验的 Go 开发者可以评论他们对这个问题的看法。

不同意 过分关心错误的内部细节 这个观点 - 因为 Go 的错误处理风格是通过代码向上冒泡。这个堆栈的最顶层应该具备所有手段来处理它,而不需要使用任何形式的字符串内省。(这就是为什么 pkg/errors 的 Wrap 一开始就很有用)。你不想包装一些不能被解包的东西。 (如果我误解了那个声明,我道歉 😊)

……不引用任何人的话;通过不同的渠道,有传言说 pkg/errors 可能会被弃用。

pkg/errors 包中,Cause() 方法确实返回的是错误链的根因错误,而不是直接原因。这是该包的设计选择,Cause() 的行为类似于 RootCause()

要解决你的问题,这里有几个方案:

1. 获取错误链中每个级别的单独消息

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

// 遍历错误链,获取每个级别的消息
func printErrorChain(err error) {
    current := err
    for i := 0; current != nil; i++ {
        fmt.Printf("%sLevel %d: %s\n", strings.Repeat("  ", i), i, current.Error())
        
        // 检查是否有包装的错误
        if wrapper, ok := current.(interface { Unwrap() error }); ok {
            current = wrapper.Unwrap()
        } else {
            break
        }
    }
}

// 或者使用类型断言来访问包装的错误
func printErrorChainWithDetails(err error) {
    current := err
    tabs := 0
    
    for current != nil {
        fmt.Printf("%s+-- %s\n", strings.Repeat("    ", tabs), current.Error())
        
        // 尝试解包
        switch e := current.(type) {
        case interface { Unwrap() error }:
            current = e.Unwrap()
            tabs++
        default:
            current = nil
        }
    }
}

2. 获取直接原因(而不是根因)

// 获取直接包装的错误
func DirectCause(err error) error {
    if wrapper, ok := err.(interface { Unwrap() error }); ok {
        return wrapper.Unwrap()
    }
    return nil
}

// 使用示例
func main() {
    e := errors.New("[Area] multiplier wasnt a positive int")
    e = errors.Wrap(e, "[main] Area failed for -1")
    e = errors.Wrap(e, "Top most error")
    
    // 获取直接原因
    directCause := DirectCause(e)
    if directCause != nil {
        fmt.Printf("Direct cause: %v\n", directCause)
    }
    
    // 遍历整个错误链
    printErrorChain(e)
}

3. 使用标准库的 errors 包(Go 1.13+)

从 Go 1.13 开始,标准库的 errors 包提供了更好的错误处理:

import (
    "errors"
    "fmt"
)

func main() {
    baseErr := errors.New("[Area] multiplier wasnt a positive int")
    err1 := fmt.Errorf("[main] Area failed for -1: %w", baseErr)
    err2 := fmt.Errorf("Top most error: %w", err1)
    
    // 获取根因
    rootCause := errors.Unwrap(errors.Unwrap(err2))
    fmt.Printf("Root cause: %v\n", rootCause)
    
    // 遍历错误链
    current := err2
    for i := 0; current != nil; i++ {
        fmt.Printf("Level %d: %s\n", i, current.Error())
        
        unwrapped := errors.Unwrap(current)
        if unwrapped == nil {
            break
        }
        current = unwrapped
    }
}

4. 使用支持错误链遍历的第三方库

如果你需要更强大的功能,可以考虑使用支持错误链遍历的库:

// 使用 hashicorp/go-multierror 或其他支持错误链的库
import "github.com/hashicorp/go-multierror"

// 或者创建自定义错误类型来更好地控制错误链
type WrappedError struct {
    msg string
    cause error
}

func (w *WrappedError) Error() string {
    return w.msg
}

func (w *WrappedError) Unwrap() error {
    return w.cause
}

func Wrap(err error, msg string) error {
    return &WrappedError{msg: msg, cause: err}
}

关键点是:pkg/errors.Cause() 确实返回根因错误,要获取直接原因需要使用解包机制。在 Go 1.13+ 中,标准库提供了更好的错误链处理能力。

回到顶部