简介
Go提供了两种在标准库中创建错误的方法errors.New
和fmt.Errorf
.当在调试时向您的用户或您将来的自己传达更复杂的错误信息时,有时这两种机制不足以充分捕获和报告所发生的事情。为了传递这种更复杂的错误信息并获得更多功能,我们可以实现标准库接口类型[error
](https://golang.org/pkg/builtin/ error).
其语法如下所示:
1type error interface {
2 Error() string
3}
Builtin
包将error
定义为具有单个Error()
方法的接口,该方法以字符串形式返回错误消息。通过实现此方法,我们可以将我们定义的任何类型转换为我们自己的错误。
让我们尝试运行以下示例,以查看error
接口的实现:
1package main
2
3import (
4 "fmt"
5 "os"
6)
7
8type MyError struct{}
9
10func (m *MyError) Error() string {
11 return "boom"
12}
13
14func sayHello() (string, error) {
15 return "", &MyError{}
16}
17
18func main() {
19 s, err := sayHello()
20 if err != nil {
21 fmt.Println("unexpected error: err:", err)
22 os.Exit(1)
23 }
24 fmt.Println("The string:", s)
25}
我们将看到以下输出:
1[secondary_label Output]
2unexpected error: err: boom
3exit status 1
这里我们创建了一个新的空结构类型MyError
,并在其上定义了Error()
方法。Error()
方法返回字符串om
。
在main()
中,我们调用函数sayHello
,该函数返回一个空字符串和一个新的MyError
实例。由于sayHello
总是返回错误,所以main()
中if语句体内的fmt.Println
调用将始终执行。然后,我们使用fmt.Println
打印短前缀字符串意外错误:
以及err
变量中保存的MyError
的实例。
请注意,我们不必直接调用Error()
,因为fmt
包能够自动检测到这是error
的实现。它调用Error()
transparently来获取字符串boom
,并将其与前缀字符串意外错误:Err:
连接在一起。
在自定义错误中收集详细信息
有时,自定义错误是捕获详细错误信息的最干净的方法。例如,假设我们想要捕获HTTP请求产生的错误的状态码;运行下面的程序来查看error
的实现,它允许我们干净地捕获该信息:
1package main
2
3import (
4 "errors"
5 "fmt"
6 "os"
7)
8
9type RequestError struct {
10 StatusCode int
11
12 Err error
13}
14
15func (r *RequestError) Error() string {
16 return fmt.Sprintf("status %d: err %v", r.StatusCode, r.Err)
17}
18
19func doRequest() error {
20 return &RequestError{
21 StatusCode: 503,
22 Err: errors.New("unavailable"),
23 }
24}
25
26func main() {
27 err := doRequest()
28 if err != nil {
29 fmt.Println(err)
30 os.Exit(1)
31 }
32 fmt.Println("success!")
33}
我们将看到以下输出:
1[secondary_label Output]
2status 503: err unavailable
3exit status 1
在本例中,我们创建了一个新的RequestError
实例,并使用标准库中的errors.New
函数提供了状态码和错误。然后,我们像前面的示例一样使用fmt.Println
打印它。
在RequestError
的Error()
方法中,我们使用fmt.Sprint tf
函数来构造一个字符串,该字符串使用在错误产生时提供的信息。
类型断言和自定义错误
error
接口只公开一个方法,但我们可能需要访问error
实现的其他方法才能正确处理错误。例如,我们可能有几个临时的、可以重试的error‘的自定义实现--由一个
Temporary()`方法表示。
接口提供了由类型提供的更广泛的方法集的狭窄视图,因此我们必须使用a_type断言_来更改该视图显示的方法,或者完全删除它。
下面的示例将前面显示的RequestError
扩展为一个Temporary()
方法,该方法将指示调用者是否应该重试该请求:
1package main
2
3import (
4 "errors"
5 "fmt"
6 "net/http"
7 "os"
8)
9
10type RequestError struct {
11 StatusCode int
12
13 Err error
14}
15
16func (r *RequestError) Error() string {
17 return r.Err.Error()
18}
19
20func (r *RequestError) Temporary() bool {
21 return r.StatusCode == http.StatusServiceUnavailable // 503
22}
23
24func doRequest() error {
25 return &RequestError{
26 StatusCode: 503,
27 Err: errors.New("unavailable"),
28 }
29}
30
31func main() {
32 err := doRequest()
33 if err != nil {
34 fmt.Println(err)
35 re, ok := err.(*RequestError)
36 if ok {
37 if re.Temporary() {
38 fmt.Println("This request can be tried again")
39 } else {
40 fmt.Println("This request cannot be tried again")
41 }
42 }
43 os.Exit(1)
44 }
45
46 fmt.Println("success!")
47}
我们将看到以下输出:
1[secondary_label Output]
2unavailable
3This request can be tried again
4exit status 1
在main()
中,我们调用doRequest()
,它向我们返回一个error
接口。我们首先打印Error()
方法返回的错误消息。接下来,我们尝试通过类型断言re,ok:=err.(* RequestError)
公开来自RequestError
的所有方法。如果类型断言成功,则使用Temporary()
方法来确定此错误是否是临时性错误。由于doRequest()
设置的StatusCode
为503
,匹配HTTP.StatusServiceUnavailable
,因此返回true
,并导致打印‘可以重试此请求’
。实际上,我们会发出另一个请求,而不是打印消息。
包装错误
通常,错误是由程序之外的东西生成的,例如:数据库、网络连接等。这些错误提供的错误消息不能帮助任何人找到错误的根源。在错误消息的开头用额外信息包装错误将为成功调试提供一些所需的上下文。
下面的示例演示了如何将一些上下文信息附加到从其他函数返回的神秘的error
中:
1package main
2
3import (
4 "errors"
5 "fmt"
6)
7
8type WrappedError struct {
9 Context string
10 Err error
11}
12
13func (w *WrappedError) Error() string {
14 return fmt.Sprintf("%s: %v", w.Context, w.Err)
15}
16
17func Wrap(err error, info string) *WrappedError {
18 return &WrappedError{
19 Context: info,
20 Err: err,
21 }
22}
23
24func main() {
25 err := errors.New("boom!")
26 err = Wrap(err, "main")
27
28 fmt.Println(err)
29}
我们将看到以下输出:
1[secondary_label Output]
2main: boom!
WrapedError
是一个结构,有两个字段:一个是作为字符串
的上下文消息,另一个是error
,这个WrapedError
提供了更多的信息。当调用Error()
方法时,我们再次使用fmt.Sprint tf
打印上下文消息,然后error
(fmt.Sprint tf
也知道隐式调用Error()
方法)。
在main()
中,我们使用errors.New
创建一个错误,然后使用我们定义的Wrap
函数包装该错误。这允许我们指出这个错误
是在main
中生成的。此外,因为我们的`WrapedError‘也是一个’Error‘,所以我们可以包装其他的’WrapedError‘--这将允许我们看到一个链来帮助我们追踪错误的来源。在标准库的帮助下,我们甚至可以在错误中嵌入完整的堆栈跟踪。
结论
由于)的所有内容。虽然Go语言中的错误处理机制表面上看起来过于简单,但我们可以使用这些自定义错误来处理常见和不常见的情况。
围棋还有另一种机制来传达意外行为--恐慌。在错误处理系列的下一篇文章中,我们将研究异常--它们是什么以及如何处理它们。