@phper
2018-03-13T18:07:22.000000Z
字数 4185
阅读 3005
Golang
原文:https://golangbot.com/error-handling/
欢迎访问Golang 系列教程中的第30章。
错误指程序中出现异常情况。比方说, 我们正在尝试打开一个文件, 文件系统中不存在该文档。这是一个异常情况, 它表示为一个错误。
Go中的错误是普通的旧值。错误使用内置error
类型表示。
就像在type
中构建的任何其他类型, 如 int、float64、... 错误值可以存储在变量中, 从函数返回等等。
让我们马上开始一个尝试打开不存在的文件的示例程序。
package main
import (
"fmt"
"os"
)
func main() {
f, err := os.Open("/test.txt")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(f.Name(), "opened successfully")
}
在上面的程序第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
是具有以下定义的接口类型,
type error interface {
Error() string
}
它只包含一个名为Error() string
的方法。任何实现此接口的类型都可以用作错误。此方法用来提供错误的说明。
打印错误时,fmt.Println
函数调用时,会自动在内部调用Error() string
方法以获取错误的说明。这就是在上述示例程序的11行中打印错误说明的方式。
现在我们知道error
是一种接口类型,让我们看看我们如何提取更多关于错误的信息。
在上面看到的示例中, 我们刚刚打印了错误的描述。如果我们想要导致错误的文件的实际路径怎么办?一种可能的方法是解析错误字符串。这是我们的输出,
open /test.txt: No such file or directory
我们可以解析这个错误信息,并得到导致错误的文件的文件路径“/test.txt”,但这是一个糟糕的方式。错误描述可以随时在新版本的语言中更改,我们的代码将会中断。
有没有办法可靠地获取文件名?答案是肯定的,它可以完成,标准的Go库使用不同的方式提供有关错误的更多信息。让我们一个一个看看他们。
如果仔细阅读Open函数的文档, 可以看到它返回一个*PathError
类型的错误。 PathError它是一个结构类型,它在标准库中的实现如下:
type PathError struct {
Op string
Path string
Err error
}
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
字段包含导致错误的文件的路径。让我们修改上面写的程序并打印路径。
package main
import (
"fmt"
"os"
)
func main() {
f, err := os.Open("/test.txt")
if err, ok := err.(*os.PathError); ok {
fmt.Println("File at path", err.Path, "failed to open")
return
}
fmt.Println(f.Name(), "opened successfully")
}
在上面的程序中, 我们使用第10行中的类型断言
来获取错误接口的基础值。然后, 我们在11行使用 err
err.Path
来打印路径。这个程序输出,
File at path /test.txt failed to open
棒极了😃。我们已成功使用类型断言
从错误中获取文件路径。
获取更多信息的第二种方法是断言基础类型, 并通过调用结构类型上的方法获取更多信息。
让我们通过一个例子来更好地理解这一点。
标准库中的 DNSError
结构类型定义如下所示,
type DNSError struct {
...
}
func (e *DNSError) Error() string {
...
}
func (e *DNSError) Timeout() bool {
...
}
func (e *DNSError) Temporary() bool {
...
}
从上面的代码中可以看到, DNSError
结构有两个方法Timeout() bool
和Temporary() bool
, 它返回一个布尔值, 指示错误是由于超时还是暂时的。
让我们编写一个程序, 它断言*DNSError
类型并调用这些方法来确定错误是临时的还是由于超时。
package main
import (
"fmt"
"net"
)
func main() {
addr, err := net.LookupHost("golangbot123.com")
if err, ok := err.(*net.DNSError); ok {
if err.Timeout() {
fmt.Println("operation timed out")
} else if err.Temporary() {
fmt.Println("temporary error")
} else {
fmt.Println("generic error: ", err)
}
return
}
fmt.Println(addr)
}
注意: DNS 查找在操场上不起作用。请在本地计算机中运行此程序。
在上面的程序中, 我们试图在第9行中获取无效域名golangbot123.com
的 ip 地址。在第10行中, 我们通过使用*net.DNSError
来断言获取错误的基本值。然后, 我们在第11和第13行来检查错误是否是由于超时还是临时。
在我们的情况下, 错误既不是临时, 也不是由于超时, 因此程序将打印,
generic error: lookup golangbot123.com: no such host
如果错误是暂时的或由于超时, 则相应的 if 语句将执行, 我们可以适当地处理它。
获取有关错误的更多详细信息的第三种方法是直接与类型为error
的变量进行比较。让我们通过一个例子来理解这一点。
filepath
包的Glob函数用于返回与模式匹配的所有文件的名称。当模式格式不正确时, 此函数返回错误ErrBadPattern
。
ErrBadPattern
在filepath
包中的定义如下。
var ErrBadPattern = errors.New("syntax error in pattern")
errors.New()
用于创建新的错误。我们将在下一篇教程中详细讨论。
当模式格式不正确时, Glob函数将返回ErrBadPattern
错误。
让我们编写一个小程序来检查这个错误。
package main
import (
"fmt"
"path/filepath"
)
func main() {
files, error := filepath.Glob("[")
if error != nil && error == filepath.ErrBadPattern {
fmt.Println(error)
return
}
fmt.Println("matched files", files)
}
在上面的程序中, 我们搜索模式为[
的文件, 这是一个格式不正确的模式。我们检查错误是否为nil。为了获取有关错误的更多信息, 我们在第10行直接将其与filepath.ErrBadPattern
进行比较。如果条件满足, 则错误是由于模式不正确造成的。该程序将打印,
syntax error in pattern
标准库使用上述任一方法提供有关错误的更多信息。我们将在下一个教程中使用这些方法来创建我们自己的自定义错误。
永远不要忽略错误。忽视错误是自找麻烦。让我重写示例, 其中列出了所有匹配模式 (忽略错误处理代码) 的文件的名称。
package main
import (
"fmt"
"path/filepath"
)
func main() {
files, _ := filepath.Glob("[")
fmt.Println("matched files", files)
}
从上一示例中我们已经知道模式无效。第9行,调用Glob
函数,使用_
空白标识符来忽略函数返回的错误。我只是打印的匹配文件在10行。该程序将打印,
matched files []
因为我们忽略了错误, 所以输出看起来好像没有文件匹配模式, 但实际上模式本身是错误的。所以千万不要忽略错误。
这使我们到本教程的末尾。
在本教程中, 我们讨论了如何处理程序中发生的错误, 以及如何检查错误以获取更多信息。快速回顾一下我们在本教程中讨论的内容,
在下一教程中, 我们将创建自己的自定义错误, 并将更多的上下文添加到标准错误中。
祝你今天开心。