[关闭]
@phper 2018-03-13T10:07:22.000000Z 字数 4185 阅读 392

30.错误处理

Golang

原文:https://golangbot.com/error-handling/

欢迎访问Golang 系列教程中的第30章。

什么是错误?

错误指程序中出现异常情况。比方说, 我们正在尝试打开一个文件, 文件系统中不存在该文档。这是一个异常情况, 它表示为一个错误。

Go中的错误是普通的旧值。错误使用内置error类型表示。

就像在type中构建的任何其他类型, 如 int、float64、... 错误值可以存储在变量中, 从函数返回等等。

例如

让我们马上开始一个尝试打开不存在的文件的示例程序。

  1. package main
  2. import (
  3. "fmt"
  4. "os"
  5. )
  6. func main() {
  7. f, err := os.Open("/test.txt")
  8. if err != nil {
  9. fmt.Println(err)
  10. return
  11. }
  12. fmt.Println(f.Name(), "opened successfully")
  13. }

在playground上运行

在上面的程序第9行中, 我们试图在路径/test.txt中打开该文件 (在操场上显然不存在)。os包的 Open 函数是这样定义的,

func Open(name string) (file *File, err error)

如果文件已成功打开, 则Open函数将返回文件处理程序, 错误将为零。如果打开文件时出现错误, 将返回一个非nil错误。

如果函数或方法返回一个错误, 则根据约定, 它必须是函数返回的最后一个值。因此, Open函数将err作为最后一个值返回。

GO中处理错误的惯用方式是将返回的错误与nil进行比较。是nil表示没有发生错误, 而非nil表示存在错误. 回到我们的例子本身, 我们在第10行判断err是否为nil。如果不是nil, 我们只需打印错误并从主函数返回。

运行此程序将打印

open /test.txt: No such file or directory  

完美😃。我们得到一个错误, 指出该文件不存在。

错误类型的表示形式

让我们深入了解一下如何定义内置的error类型。error是具有以下定义的接口类型,

  1. type error interface {
  2. Error() string
  3. }

它只包含一个名为Error() string的方法。任何实现此接口的类型都可以用作错误。此方法用来提供错误的说明。

打印错误时,fmt.Println函数调用时,会自动在内部调用Error() string方法以获取错误的说明。这就是在上述示例程序的11行中打印错误说明的方式。

从错误中提取更多信息的不同方法

现在我们知道error是一种接口类型,让我们看看我们如何提取更多关于错误的信息。

在上面看到的示例中, 我们刚刚打印了错误的描述。如果我们想要导致错误的文件的实际路径怎么办?一种可能的方法是解析错误字符串。这是我们的输出,

open /test.txt: No such file or directory  

我们可以解析这个错误信息,并得到导致错误的文件的文件路径“/test.txt”,但这是一个糟糕的方式。错误描述可以随时在新版本的语言中更改,我们的代码将会中断。

有没有办法可靠地获取文件名?答案是肯定的,它可以完成,标准的Go库使用不同的方式提供有关错误的更多信息。让我们一个一个看看他们。

1.断言底层结构类型并从结构字段获取更多信息

如果仔细阅读Open函数的文档, 可以看到它返回一个*PathError类型的错误。 PathError它是一个结构类型,它在标准库中的实现如下:

  1. type PathError struct {
  2. Op string
  3. Path string
  4. Err error
  5. }
  6. func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }

如果您有兴趣知道上面的源代码在哪里, 可以在这里找到:https://golang.org/src/os/error.go?s=653:716#L11

从上面的代码中, 您可以理解*PathError通过声明Error() string方法来实现error interface。此方法将操作、路径和实际错误串联起来, 并返回它。因此, 我们得到了错误信息,

open /test.txt: No such file or directory 

PathError结构的Path字段包含导致错误的文件的路径。让我们修改上面写的程序并打印路径。

  1. package main
  2. import (
  3. "fmt"
  4. "os"
  5. )
  6. func main() {
  7. f, err := os.Open("/test.txt")
  8. if err, ok := err.(*os.PathError); ok {
  9. fmt.Println("File at path", err.Path, "failed to open")
  10. return
  11. }
  12. fmt.Println(f.Name(), "opened successfully")
  13. }

在playground上运行

在上面的程序中, 我们使用第10行中的类型断言来获取错误接口的基础值。然后, 我们在11行使用 err err.Path来打印路径。这个程序输出,

File at path /test.txt failed to open  

棒极了😃。我们已成功使用类型断言从错误中获取文件路径。

2. 断言底层结构类型并使用方法获取更多信息

获取更多信息的第二种方法是断言基础类型, 并通过调用结构类型上的方法获取更多信息。

让我们通过一个例子来更好地理解这一点。

标准库中的 DNSError 结构类型定义如下所示,

  1. type DNSError struct {
  2. ...
  3. }
  4. func (e *DNSError) Error() string {
  5. ...
  6. }
  7. func (e *DNSError) Timeout() bool {
  8. ...
  9. }
  10. func (e *DNSError) Temporary() bool {
  11. ...
  12. }

从上面的代码中可以看到, DNSError结构有两个方法Timeout() boolTemporary() bool , 它返回一个布尔值, 指示错误是由于超时还是暂时的。

让我们编写一个程序, 它断言*DNSError类型并调用这些方法来确定错误是临时的还是由于超时。

  1. package main
  2. import (
  3. "fmt"
  4. "net"
  5. )
  6. func main() {
  7. addr, err := net.LookupHost("golangbot123.com")
  8. if err, ok := err.(*net.DNSError); ok {
  9. if err.Timeout() {
  10. fmt.Println("operation timed out")
  11. } else if err.Temporary() {
  12. fmt.Println("temporary error")
  13. } else {
  14. fmt.Println("generic error: ", err)
  15. }
  16. return
  17. }
  18. fmt.Println(addr)
  19. }

注意: DNS 查找在操场上不起作用。请在本地计算机中运行此程序。

在上面的程序中, 我们试图在第9行中获取无效域名golangbot123.com的 ip 地址。在第10行中, 我们通过使用*net.DNSError来断言获取错误的基本值。然后, 我们在第11和第13行来检查错误是否是由于超时还是临时。

在我们的情况下, 错误既不是临时, 也不是由于超时, 因此程序将打印,

generic error:  lookup golangbot123.com: no such host  

如果错误是暂时的或由于超时, 则相应的 if 语句将执行, 我们可以适当地处理它。

3. 直接比较

获取有关错误的更多详细信息的第三种方法是直接与类型为error的变量进行比较。让我们通过一个例子来理解这一点。

filepath包的Glob函数用于返回与模式匹配的所有文件的名称。当模式格式不正确时, 此函数返回错误ErrBadPattern

ErrBadPatternfilepath包中的定义如下。

var ErrBadPattern = errors.New("syntax error in pattern") 

errors.New()用于创建新的错误。我们将在下一篇教程中详细讨论。

当模式格式不正确时, Glob函数将返回ErrBadPattern错误。

让我们编写一个小程序来检查这个错误。

  1. package main
  2. import (
  3. "fmt"
  4. "path/filepath"
  5. )
  6. func main() {
  7. files, error := filepath.Glob("[")
  8. if error != nil && error == filepath.ErrBadPattern {
  9. fmt.Println(error)
  10. return
  11. }
  12. fmt.Println("matched files", files)
  13. }

在playground上运行

在上面的程序中, 我们搜索模式为[的文件, 这是一个格式不正确的模式。我们检查错误是否为nil。为了获取有关错误的更多信息, 我们在第10行直接将其与filepath.ErrBadPattern进行比较。如果条件满足, 则错误是由于模式不正确造成的。该程序将打印,

syntax error in pattern  

标准库使用上述任一方法提供有关错误的更多信息。我们将在下一个教程中使用这些方法来创建我们自己的自定义错误。

不忽略错误

永远不要忽略错误。忽视错误是自找麻烦。让我重写示例, 其中列出了所有匹配模式 (忽略错误处理代码) 的文件的名称。

  1. package main
  2. import (
  3. "fmt"
  4. "path/filepath"
  5. )
  6. func main() {
  7. files, _ := filepath.Glob("[")
  8. fmt.Println("matched files", files)
  9. }

在playground上运行

从上一示例中我们已经知道模式无效。第9行,调用Glob 函数,使用_空白标识符来忽略函数返回的错误。我只是打印的匹配文件在10行。该程序将打印,

matched files []  

因为我们忽略了错误, 所以输出看起来好像没有文件匹配模式, 但实际上模式本身是错误的。所以千万不要忽略错误。

这使我们到本教程的末尾。

在本教程中, 我们讨论了如何处理程序中发生的错误, 以及如何检查错误以获取更多信息。快速回顾一下我们在本教程中讨论的内容,

在下一教程中, 我们将创建自己的自定义错误, 并将更多的上下文添加到标准错误中。

祝你今天开心。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注