在Go中创建自定义错误的方式总结
作者:CHQIUU
引言
Go提供了两种在标准库中创建错误的方法,[errors.New
和fmt.Errorf
],当与用户交流更复杂的错误信息时,或在调试时与未来的自己交流时,有时这两种机制不足以充分捕获和报告所发生的情况。为了传达更复杂的错误信息并实现更多的功能,我们可以实现标准库接口类型,error。
其语法如下:
type error interface { Error() string }
builtin包将error
定义为一个接口,它只有一个Error()
方法,返回一个字符串形式的错误消息。通过实现这个方法,我们可以将定义的任何类型转换为我们自己的error。
让我们尝试运行以下示例来查看error
接口的实现:
package main import ( "fmt" "os" ) type MyError struct{} func (m *MyError) Error() string { return "boom" } func sayHello() (string, error) { return "", &MyError{} } func main() { s, err := sayHello() if err != nil { fmt.Println("unexpected error: err:", err) os.Exit(1) } fmt.Println("The string:", s) }
Outputunexpected error: err: boom exit status 1
在这里,我们创建了一个新的空结构类型MyError
,并在其上定义了Error()
方法。Error()
方法返回字符串"boom"
。
在main()
中,我们调用函数sayHello
,该函数返回一个空字符串和一个新的MyError
实例。由于sayHello
总是会返回错误,所以main()
中的if语句体中的fmt.Println
调用总是会执行。然后,我们使用fmt.Println
打印短前缀字符串"unexpected error:"
以及err
变量中保存的MyError
实例。
请注意,我们不必直接调用Error()
,因为fmt
包能够自动检测这是Error
的实现。它透明地调用Error()
来获取字符串"boom"
,并将其与前缀字符串"unexpected Error: err:"
连接起来。
在自定义错误中收集详细信息
有时候,自定义错误是捕获详细错误信息的最简洁方式。例如,假设我们想要捕获HTTP请求产生的错误的状态码;运行以下程序来查看error
的实现,它允许我们清晰地捕获信息:
package main import ( "errors" "fmt" "os" ) type RequestError struct { StatusCode int Err error } func (r *RequestError) Error() string { return fmt.Sprintf("status %d: err %v", r.StatusCode, r.Err) } func doRequest() error { return &RequestError{ StatusCode: 503, Err: errors.New("unavailable"), } } func main() { err := doRequest() if err != nil { fmt.Println(err) os.Exit(1) } fmt.Println("success!") }
Outputstatus 503: err unavailable
exit status 1
在这个例子中,我们创建了一个新的RequestError
实例,并使用标准库中的errors.New
函数提供状态码和一个错误。然后像前面的例子一样,我们使用fmt.Println
打印它。
在RequestError
的Error()
方法中,我们使用fmt.Sprintf
函数来使用创建错误时提供的信息来构造一个字符串。
类型断言和自定义错误
error
接口只公开了一个方法,但我们可能需要访问error
实现的其他方法来正确处理错误。例如,我们可能有几个临时的error
自定义实现,可以通过存在的 temporary()
方法进行检索。
接口为类型提供了更广泛的方法集合,因此我们必须使用类型断言来更改view正在显示的方法,或者完全删除它。
下面的例子在之前的RequestError
的基础上增加了一个Temporary()
方法,该方法将表明调用者是否应该重试请求:
package main import ( "errors" "fmt" "net/http" "os" ) type RequestError struct { StatusCode int Err error } func (r *RequestError) Error() string { return r.Err.Error() } func (r *RequestError) Temporary() bool { return r.StatusCode == http.StatusServiceUnavailable // 503 } func doRequest() error { return &RequestError{ StatusCode: 503, Err: errors.New("unavailable"), } } func main() { err := doRequest() if err != nil { fmt.Println(err) re, ok := err.(*RequestError) if ok { if re.Temporary() { fmt.Println("This request can be tried again") } else { fmt.Println("This request cannot be tried again") } } os.Exit(1) } fmt.Println("success!") }
Outputunavailable
This request can be tried again
exit status 1
在main()
中,我们调用doRequest()
,它会返回一个error
接口给我们。我们首先打印 error()
方法返回的错误消息。接下来,我们尝试使用类型断言re, ok := err.(*RequestError)
来暴露RequestError
中的所有方法。如果类型断言成功,那么我们使用Temporary()
方法来查看此错误是否为临时错误。由于doRequest()
设置的StatusCode
是503
,这与http.StatusServiceUnavailable
匹配,因此返回true
并导致打印" this request can be try again"
。在实践中,我们会发送另一个请求,而不是打印一条消息。
包装错误
通常,错误会在程序之外产生,例如:数据库、网络连接等。这些错误提供的错误消息不能帮助任何人找到错误的根源。在错误消息的开头使用额外的信息包装错误,可以为成功调试提供一些必要的上下文。
下面的例子演示了我们如何将一些上下文信息附加到从其他函数返回的晦涩的error
上:
package main import ( "errors" "fmt" ) type WrappedError struct { Context string Err error } func (w *WrappedError) Error() string { return fmt.Sprintf("%s: %v", w.Context, w.Err) } func Wrap(err error, info string) *WrappedError { return &WrappedError{ Context: info, Err: err, } } func main() { err := errors.New("boom!") err = Wrap(err, "main") fmt.Println(err) }
Outputmain: boom!
WrappedError
是一个有两个字段的结构体:一个是string
类型的上下文消息,另一个是error
类型,WrappedError
提供了更多的信息。当Error()
方法被调用时,我们再次使用fmt.Sprintf
来打印上下文消息,然后Error
(fmt.Sprintf
也知道隐式调用Error()
方法)。
在main()
中,我们使用errors.New
创建一个错误,然后使用我们定义的wrap
函数包装这个错误。这允许我们指出这个error
是在"main"
中生成的。此外,由于我们的WrappedError
也是一个error
,我们可以包装其他的WrappedError
,这将允许我们看到一个链来帮助我们追踪错误的来源。在标准库的帮助下,我们甚至可以在错误中嵌入完整的堆栈跟踪。
总结
由于error
接口只有一个方法,我们已经看到我们可以为不同的情况提供不同类型的错误。这可以涵盖从将多条信息作为错误的一部分进行沟通到实现指数回退的所有事情。虽然Go中的错误处理机制表面上看起来很简单,但我们可以使用这些自定义错误来处理常见和不常见的情况,从而实现相当丰富的处理。
Go还有另一种沟通意外行为的机制panics。在错误处理系列的下一篇文章中,我们将研究恐慌——它们是什么以及如何处理它们。
到此这篇关于在Go中创建自定义错误的文章就介绍到这了,更多相关Go创建自定义错误内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!