二十九.golang的错误处理
错误表示程序中出现了异常情况。比如当我们试图打开一个文件时,文件系统里却并没有这个文件。这就是异常情况,它用一个错误来表示。
在 Go 中,错误一直是很常见的。错误用内建的 error 类型来表示。
就像其他的内建类型(如 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") }
func Open(name string) (file *File, err error)
如果一个[函数] 或[方法] 返回了错误,按照惯例,错误会作为最后一个值返回。于是 Open 函数也是将 err 作为最后一个返回值。
按照 Go 的惯例,在处理错误时,通常都是将返回的错误与 nil 比较。nil 值表示了没有错误发生,而非 nil 值表示出现了错误。在这里,我们第 10 行检查了错误值是否为 nil。如果不是 nil,我们会简单地打印出错误,并在 main 函数中返回。
运行该程序会输出:
open /test.txt: No such file or directory
很棒!我们得到了一个错误,它指出该文件并不存在。
type error interface {
Error() string
}
error 有了一个签名为 Error() string 的方法。所有实现该接口的类型都可以当作一个错误类型。Error()
fmt.Println 在打印错误时,会在内部调用 Error() string 方法来得到该错误的描述。上一节示例中的第 11 行,就是这样打印出错误的描述的。
现在,我们知道了 error 是一个接口类型,让我们看看如何从一个错误获取更多信息。
open /test.txt: No such file or directory
有没有更加可靠的方法来获取文件名呢?答案是肯定的,这是可以做到的,Go 标准库给出了各种提取错误相关信息的方法。我们一个个来看看吧。
type PathError struct { Op string Path string Err error } func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }
open /test.txt: No such file or directory
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") }
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) }
在上述程序中,我们在第 9 行,试图获取 golangbot123.com(无效的域名) 的 ip。在第 10 行,我们通过 *net.DNSError 的类型断言,获取到了错误的底层值。接下来的第 11 行和第 13 行,我们分别检查了该错误是由超时引起的,还是一个临时性错误。
在本例中,我们的错误既不是临时性错误,也不是由超时引起的,因此该程序输出:
generic error: lookup golangbot123.com: no such host
如果该错误是临时性错误,或是由超时引发的,那么对应的 if 语句会执行,于是我们就可以适当地处理它们。
第三种获取错误的更多信息的方式,是与 error 类型的变量直接比较。我们通过一个示例来理解。
filepath 包中的 [Glob
filepath 包中的 ErrBadPattern 定义如下:
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) }
syntax error in pattern
标准库在提供错误的详细信息时,使用到了上述提到的三种方法。在下一教程里,我们会通过这些方法来创建我们自己的自定义错误。
package main import ( "fmt" "path/filepath" ) func main() { files, _ := filepath.Glob("[") fmt.Println("matched files", files) }
matched files []
由于我忽略了错误,输出看起来就像是没有任何匹配了 glob 模式的文件,但实际上这是因为模式的写法不对。所以绝不要忽略错误。
这一教程我们讨论了该如何处理程序中出现的错误,也讨论了如何查询关于错误的更多信息。简单概括一下本教程讨论的内容:
-
什么是错误?
-
错误的表示
-
获取错误详细信息的各种方法
-
不能忽视错误