本文将介绍 Go 中的错误处理,以及为什么我们需要错误处理。
什么是错误处理
异常处理是任何语言都不能绕不开的话题。Go 语言没有提供传统的 try...catch
语句来处理异常,而是通过使用 error
来处理错误,用 panic
和 recover
来处理异常。
错误封装是将一个错误包裹到另一个错误的过程。假设我们有一个访问数据库的 Web 服务器,并试图从数据库中获取一条记录。如果数据库调用返回一个错误,我们可以决定是捕获这个错误还是从网络服务中发送我们自己的自定义错误。
基础 error
error 接口
error 是一种内建的接口类型,内建意味着不需要 import
任何包就可以直接使用,使用起来就像基础类型一样自然。
type error interface {
Error() string
}
复制代码
error 接口只声明了一个 Error()
方法,任何实现了该方法的结构体都可以作为 error 来使用。
error
的实例代表一种异常状态
Error()
方法用于描述该异常状态
值为 nil
的 error 代表没有异常
标准库 errors
包中的 errorString
就是实现 error 接口的一个例子:
type errorString struct {
s string
}
func (e *errorString) Error() string {
return.s
}
复制代码
创建 error
标准库提供了两种 error 的方法:
error.New()
的实现极其简单,只是简单地构造一个 errorString
实例便返回:
package errors
func New(text string) error {
return &errorString{text}
}
复制代码
fmt.Errorf()
error.New()
单调地接收一个字符串参数来构造 error,而实际场景中往往需要使用 fmt.Sprintf()
生成字符串,这时可以直接使用 fmt.Errorf()
:
package fmt
func Errorf(format string, a ...interface{}) error {
return errors.New(Sprintf(format, a...))
复制代码
联系与区别: fmt.Errorf()
只是对 error.New()
的简单封装,使用前者可以使得代码更加简洁。而两者的区别在于 fmt.Errorf()
适用于需要格式化输出错误字符串的场景,如果不需要格式化字符串,则建议使用 error.New()
。
异常处理
针对 error 而言,异常处理包括如何检查错误,如何传递错误。
检查 error
最常见的检查 error
的方式是与 nil
值进行比较:
if err != nil {
// 错误处理逻辑
}
复制代码
与预定义的 error 进行比较(可以是自定义也可以是标准库中的,比如 os
库中的常见错误):
var noRows = errors.New("no rows found")
if err == noRows {
}
复制代码
传递 error
在一个函数中收到一个 error,往往需要附加一些上下文信息再把 error 继续向上层抛。
可以看一下如下的例子:
package main
import (
"errors"
"fmt"
)
var noRows = errors.New("no rows found")
func getRecords() error {
return noRows
}
func webService() error {
if err := getRecords(); err != nil {
return fmt.Errorf("Error %s when calling DB", err)
}
return nil
}
func main() {
if err := webService(); err != nil {
fmt.Printf("Error: %s when calling webService\n", err)
return
}
fmt.Println("webService call successful")
}
复制代码
运行结果:
$ go run .
Error: Error no rows found when calling DB when calling webService
复制代码
在上面的程序中,我们在调用 getRecords
函数时,发送错误的字符串描述,虽然这可能看起来像错误处理,但实际上并不是。让我们接着看下去吧。
错误处理和 Is 函数
Go 语言中的 errors 包中的 Is
函数会判断目标是否有相应的错误匹配的上,在我们上一节的例子中,从 getRecords
函数中返回 nowRows
错误,然后这个错误的字符串信息从 webService
函数中返回,如果使用上 Is
函数,判断有没有查询到数据,然后才返回 noRows
错误:
package main
import (
"errors"
"fmt"
)
var noRows = errors.New("no rows found")
func getRecords() error {
return noRows
}
func webService() error {
if err := getRecords(); err != nil {
return fmt.Errorf("Error %s when calling DB", err)
}
return nil
}
func main() {
if err := webService(); err != nil {
if errors.Is(err, noRows) {
fmt.Printf("The searched record cannot be found. Error returned from DB is %s\n", err)
return
}
fmt.Println("unknown error when searching for records")
return
}
fmt.Println("webService call successful")
}
复制代码
在上面的 main
函数中,我们利用 Is
函数检查这个错误是否包含 noRows
错误。
因为 if errors.Is(err, noRows)
并不满足,所以这个 if
块里并不会执行。为了使得这个错误生效,我们需要 webService
函数返回 noRows
错误时对其进行封装。
一种方法是在返回错误时使用 %w
格式指定符,而不是 %s
。因此,可以把返回错误的那一行代码修改为:
return fmt.Errorf("Error %w when calling DB", err)
复制代码
这意味着新返回的错误包裹了原来的 noRows
,并且上述主函数 if
条件将成功生效,下面是修改的程序:
package main
import (
"errors"
"fmt"
)
var errNoRows = errors.New("no rows found")
func getRecords() error {
return errNoRows
}
func webService() error {
if err := getRecords(); err != nil {
return fmt.Errorf("error %w when calling DB", err)
}
return nil
}
func main() {
if err := webService(); err != nil {
if errors.Is(err, errNoRows) {
fmt.Printf("The searched record cannot be found. Error returned from DB is %s\n", err)
return
}
fmt.Println("unknown error when searching for records")
return
}
fmt.Println("webService call successful")
}
复制代码
运行结果:
$ go run .
The searched record cannot be found. Error returned from DB is error no rows found when calling DB
复制代码
As 函数
errors 包中的 As
函数尝试把输入的错误转换为目标错误类型。如果错误链中的任何一个错误与目标错误匹配,就返回 true。
package main
import (
"errors"
"fmt"
)
type DBError struct {
desc string
}
func (dbError DBError) Error() string {
return dbError.desc
}
func getRecords() error {
return DBError{
desc: "no rows found",
}
}
func webService() error {
if err := getRecords(); err != nil {
return fmt.Errorf("Error %w when calling DB", err)
}
return nil
}
func main() {
if err := webService(); err != nil {
var dbError DBError
if errors.As(err, &dbError) {
fmt.Printf("The searched record cannot be found. Error returned from DB is %s", dbError)
return
}
fmt.Println("unknown error when searching records")
return
}
fmt.Println("webservice call successful")
}
复制代码
在上面的程序中,我们修改了 getRecord
函数,返回一个 DBError
类型的自定义错误。
在 main
函数中,我们试图将 webService()
函数调用返回的错误转换为 DBError
类型。if errors.As(err, &dbError)
语句将会成功,因为我们已经把错误包起来了。运行这个代码,将会返回:
The searched record cannot be found. Error returned from DB is no rows found
复制代码
总结
程序可能会随时都会出现异常,需要我们在开发过程中提前做好异常处理。所以本文介绍了 Go 语言中的错误处理,Go 标准库提供的两种创建 error
的方式,并介绍了如何检查错误和如果传递错误。
希望本文能对你有所帮助,如果喜欢本文,可以点个关注.下一篇文章见!
宇宙古今无有穷期,一生不过须臾,当思奋争。
评论