🌯 围棋中的缠绕和解绕错误

98 阅读4分钟

在Go中包装错误是指在返回的错误中添加额外的上下文信息,如发生错误的函数名称、原因、类型等。这种技术最常用于创建清晰的错误信息,当你想快速准确地定位问题的来源时,这对调试工作特别有用。

要在Go中包装一个错误,你需要使用
fmt.Errorf(format string, a ...interface{}) error
函数创建一个新的错误,
format 中使用动词**%w** 。

var ErrorCritical = errors.New("critical error")
...
wrapped := fmt.Errorf("[functionName] internal error: %w", ErrorCritical)

由此产生的错误是一个链,其中包裹的错误可以使用 errors.Unwrap()函数 "解开"。

fmt.Println(errors.Unwrap(wrapped) == ErrorCritical) // true

也可以检查一个给定的错误是否存在于链中的任何地方,这要感谢 errors.Is()errors.As()函数。关于如何包装、解包和测试错误类型的细节,请看下面的例子。

查看更多在Go中使用and处理错误的例子 errors.Is()errors.As()函数处理错误的更多例子,请看我们的其他教程

例子

在第一个例子中,我们创建了getError() 函数,该函数根据参数设置返回一个非包覆、单包覆或双包覆错误。我们包住的错误是一个简单的内置error 实例。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package main
import (
"errors"
"fmt"
)
var (
ErrorInternal = errors.New("internal error")
)
func getError(level int) error {
level1Err := fmt.Errorf("[getData] level 1 error: %w", ErrorInternal)
if level == 1 {
return level1Err
}
if level == 2 {
return fmt.Errorf("[getData] level 2 error: %w", level1Err)
}
return ErrorInternal
}
func main() {
err := getError(1)
if errors.Is(err, ErrorInternal) {
fmt.Printf("is error internal: %v\n", err)
}
fmt.Printf("unwrapped error: %v\n", errors.Unwrap(err))
fmt.Printf("---\n")
err = getError(2)
if errors.Is(err, ErrorInternal) {
fmt.Printf("is error internal: %v\n", err)
}
unwrapped := errors.Unwrap(err)
fmt.Printf("unwrapped error: %v\n", unwrapped)
fmt.Printf("unwrapped unwrapped error: %v\n", errors.Unwrap(unwrapped))
}

输出。

is error internal: [getData] level 1 error: internal error
unwrapped error: internal error
---
is error internal: [getData] level 2 error: [getData] level 1 error: internal error
unwrapped error: [getData] level 1 error: internal error
unwrapped unwrapped error: internal error

让我们看一下main() 函数和输出。在第25-29 行,我们得到一个单一的包裹错误,并测试它是否是一个ErrorInternal 错误。正如你所看到的,该 errors.Is()函数返回true ,因为它检查链中的任何错误是否与目标匹配。错误是否被包起来并不重要。在这种情况下,一个简单的比较if err == ErrorInternal ,会得到false ,所以一般来说,使用 errors.Is()函数来比较错误是否相等。然后,我们用 errors.Unwrap()并将其打印到标准输出。解除对错误的包装后,就会得到我们之前包装的ErrorInternal

在第33-39 行中,我们得到一个双重包装的错误。该 errors.Is()返回ErrorInternal 在链中,尽管它是双包的。正如你所期望的那样,需要进行双重解包才能得到ErrorInternal


同样的,你也可以对特定类型的错误进行包装、解包和测试。请看下面的第二个例子。其结果与第一个例子类似,都是一个简单的error 实例。唯一的区别是使用了 errors.As()函数而不是 errors.Is()来检查链中的错误是否属于特定类型。

更多错误处理的例子,使用 errors.Is()errors.As()可以在这里找到。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package main
import (
"errors"
"fmt"
)
type ErrorInternal struct {
function string
}
func (e *ErrorInternal) Error() string {
return fmt.Sprintf("[%s] error internal", e.function)
}
func getError(level int) error {
level1Err := fmt.Errorf("level 1 error: %w", &ErrorInternal{function: "getData"})
if level == 1 {
return level1Err
}
if level == 2 {
return fmt.Errorf("level 2 error: %w", level1Err)
}
return &ErrorInternal{function: "getData"}
}
func main() {
err := getError(1)
var internalErr *ErrorInternal
if errors.As(err, &internalErr) {
fmt.Printf("is error internal: %v\n", err)
}
fmt.Printf("unwrapped error: %v\n", errors.Unwrap(err))
fmt.Printf("---\n")
err = getError(2)
if errors.As(err, &internalErr) {
fmt.Printf("is error internal: %v\n", err)
}
unwrapped := errors.Unwrap(err)
fmt.Printf("unwrapped error: %v\n", unwrapped)
fmt.Printf("unwrapped unwrapped error: %v\n", errors.Unwrap(unwrapped))
}

输出。

is error internal: level 1 error: [getData] error internal
unwrapped error: [getData] error internal
---
is error internal: level 2 error: level 1 error: [getData] error internal
unwrapped error: level 1 error: [getData] error internal
unwrapped unwrapped error: [getData] error internal