分类: Development Go 编程语言 其他
2019-09-04 17:38:25
Go提供了两种在标准库中创建错误的方法, errors.New
和fmt.Errorf
。 在向用户传达更复杂的错误信息时,或在调试时向未来的自己传达错误信息时,有时这两种机制不足以充分捕获和报告发生的情况。 为了传达这种更复杂的错误信息并获得更多功能,我们可以实现标准的库接口类型, error
。
其语法如下:
type error interface {
Error() string
}
builtin
包将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)
}
我们将看到以下输出:
unexpected 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!")
}
我们将看到以下输出:
status 503: err unavailable
exit status 1
在此示例中,我们创建了一个新的RequestError
实例,并使用标准库中的errors.New
函数提供状态代码和错误。 然后,我们使用fmt.Println
打印它,如前面的示例所示。
在RequestError
的Error()
方法中,我们使用fmt.Sprintf
函数使用创建错误时提供的信息构造字符串。
error
接口只公开一种方法,但我们可能需要访问其他error
实现方法才能正确处理错误。 例如,我们可能有几个临时的自定义error
实现,可以重试 - 由Temporary()
方法表示。
接口提供了由类型提供的更广泛的方法集的狭窄视图,因此我们必须使用类型断言来更改视图正在显示的方法,或者完全删除它。
以下示例将前面显示的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!")
}
我们将看到以下输出:
unavailable
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 tried 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)
}
我们将看到以下输出:
main: boom!
WrappedError
是一个包含两个字段的结构:一个上下文消息作为string
,以及此WrappedError
提供有关的更多信息的error
。 当调用Error()
方法时,我们再次使用fmt.Sprintf
打印上下文消息,然后发生error
( fmt.Sprintf
知道隐式调用Error()
方法)。
在main()
,我们使用errors.New
创建一个错误,然后我们使用我们定义的Wrap
函数包装该错误。 这允许我们指示此error
是在"main"
生成的。 此外,由于我们的WrappedError
也是一个error
,我们可以包装其他WrappedError
- 这将允许我们看到一个链来帮助我们追踪错误的来源。 在标准库的帮助下,我们甚至可以在错误中嵌入完整的跟踪。
由于error
接口只是一种方法,我们已经看到我们在为不同情况提供不同类型的错误方面具有很大的灵活性。 这可以涵盖从将多条信息作为错误的一部分传递到实现指数退避的所有内容 。 虽然Go中的错误处理机制可能表面上看似简单,但我们可以使用这些自定义错误来处理相当丰富的处理,以处理常见和不常见的情况。
Go有另一种机制来传达意外行为,恐慌。 在我们错误处理系列的下一篇文章中,我们将研究恐慌 - 它们是什么以及如何处理它们。
关注云架构公众号
Linux入门
QQ交流群:308781113