创建 errors
创建 errors
- 原文地址:https://peter.bourgon.org/blog/2019/09/11/programming-with-errors.html
- 原文作者:Peter
- 译文地址:https://github.com/watermelo/dailyTrans
- 译者:咔叽咔叽
- 译者水平有限,如有翻译或理解谬误,烦请帮忙指出
Go 1.13 引入了一个增强的package errors,大致标准化了错误处理。就个人而言,我觉得它的 API 令人有点困惑。本文提供一些如何更有效使用它的参考。
创建 errors
sentinel errors(译者注:表示在此错误中断,程序不会继续往下处理)和以前一样。将它们命名为 ErrXxx,使用 errors.New 来创建它们。
1var ErrFoo = errors.New("foo error")
错误类型基本上也和以前一样。将它们命名为 XxxError,并确保它们具有 Error 方法,以满足 error 接口。
1type BarError struct {
2 Reason string
3}
4
5func (e BarError) Error() string {
6 return fmt.Sprintf("bar error: %s", e.Reason)
7}
如果你的错误类型包装了另一个错误,就需要提供 Unwrap 方法。
1type BazError struct {
2 Reason string
3 Inner error
4}
5
6func (e BazError) Error() string {
7 if e.Inner != nil {
8 return fmt.Sprintf("baz error: %s: %v", e.Reason, e.Inner)
9 }
10 return fmt.Sprintf("baz error: %s", e.Reason)
11}
12
13func (e BazError) Unwrap() error {
14 return e.Inner
15}
包装和返回错误
默认情况下,当你在函数中遇到错误并需要将其返回给调用者时,可以通过 fmt.Errorf 的 %w
格式,使用相关上下文包装错误。
1func process(j Job) error {
2 result, err := preprocess(j)
3 if err != nil {
4 return fmt.Errorf("error preprocessing job: %w", err)
5 }
此过程称为错误注解。需要避免返回未注解的错误,因为这可能使调用者不知道出错的地方在哪里。
另外,考虑通过自定义错误类型(如上面的 BazError)包装错误,以获得更复杂的用例。
1p := getPriority()
2widget, err := manufacture(p, result)
3if err != nil {
4 return ManufacturingError{Priority: p, Error: err}
5}
错误检查
大多数情况下,当你收到错误时,不需要关心细节。如果你的代码执行失败了,你需要报出错误(例如记录它)并继续;或者,如果无法继续,可以使用上下文来注解错误,并将其返回给调用者。
如果你想知道收到的是哪个错误,可以用 errors.Is 检查 sentinel errors,也可以用 errors.As来检查错误值。
1err := f()
2if errors.Is(err, ErrFoo) {
3 // you know you got an ErrFoo
4 // respond appropriately
5}
6
7var bar BarError
8if errors.As(err, &bar) {
9 // you know you got a BarError
10 // bar's fields are populated
11 // respond appropriately
12}
errors.Is 和 errors.As 会尝试以递归的方式解包错误来找到匹配项。此代码演示了基本的错误包装和检查技术(译者注:需要科学上网,把这段代码贴到文章末尾了)。查看 func a()
中检查的顺序,然后尝试更改 func c()
返回的错误,以获得关于运行的流程。
正如文档所述,更偏向使用 errors.Is 来检查普通等式,例如 if err == ErrFoo
;更偏向使用 errors.As 来断言普通类型,例如 if e,ok := err.(MyError)
,因为普通版本不执行 unwrap 操作。如果你明确不希望调用者 unwrap 错误,可以为 fmt.Errorf
提供不同的格式化动词,例如 %v
;或者不要在错误类型上提供 Unwrap
方法。但这些例不是常见的。
示例
1package main
2
3import (
4 "errors"
5 "fmt"
6 "log"
7)
8
9func main() {
10 i, err := a()
11 log.Printf("i=%d err=%v", i, err)
12}
13
14//
15//
16//
17
18func a() (int, error) {
19 i, err := b()
20 if errors.Is(err, ErrFoo) {
21 return 0, fmt.Errorf("tragedy: %w", err)
22 }
23
24 var bar BarError
25 if errors.As(err, &bar) {
26 return 0, fmt.Errorf("comedy: %w", err)
27 }
28
29 var baz BazError
30 if errors.As(err, &baz) {
31 return 0, fmt.Errorf("farce: %w", err)
32 }
33
34 return i, nil
35}
36
37func b() (int, error) {
38 if err := c(); err != nil {
39 return 0, fmt.Errorf("error executing c: %w", err)
40 }
41 return 1, nil
42}
43
44func c() error {
45 // return ErrFoo
46 // return BarError{Reason: "😫"}
47 // return BazError{Reason: "☹️"}
48 return BazError{Reason: "😟", Inner: ErrFoo}
49}
50
51//
52//
53//
54
55var ErrFoo = errors.New("foo error")
56
57//
58//
59//
60
61type BarError struct {
62 Reason string
63}
64
65func (e BarError) Error() string {
66 return fmt.Sprintf("bar error: %s", e.Reason)
67}
68
69//
70//
71//
72
73type BazError struct {
74 Reason string
75 Inner error
76}
77
78func (e BazError) Unwrap() error {
79 fmt.Println("fuck")
80 return e.Inner
81}
82
83func (e BazError) Error() string {
84 if e.Inner != nil {
85 return fmt.Sprintf("baz error: %s: %v", e.Reason, e.Inner)
86 }
87 return fmt.Sprintf("baz error: %s", e.Reason)
88}