写点什么

Go 语言入门很简单:Go 语言的错误处理

作者:宇宙之一粟
  • 2022-10-18
    广东
  • 本文字数:3087 字

    阅读完需:约 10 分钟

Go 语言入门很简单:Go 语言的错误处理

本文将介绍 Go 中的错误处理,以及为什么我们需要错误处理。

什么是错误处理

异常处理是任何语言都不能绕不开的话题。Go 语言没有提供传统的 try...catch 语句来处理异常,而是通过使用 error 来处理错误,用 panicrecover 来处理异常。


错误封装是将一个错误包裹到另一个错误的过程。假设我们有一个访问数据库的 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 的方法:


  • errors.New()

  • fmt.Errorf()


  1. error.New() 的实现极其简单,只是简单地构造一个 errorString 实例便返回:


package errors
func New(text string) error { return &errorString{text}}
复制代码


  1. 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 而言,异常处理包括如何检查错误,如何传递错误。


  1. 检查 error


最常见的检查 error 的方式是与 nil 值进行比较:


if err != nil {    // 错误处理逻辑}
复制代码


与预定义的 error 进行比较(可以是自定义也可以是标准库中的,比如 os 库中的常见错误):


var noRows = errors.New("no rows found")if err == noRows {    }
复制代码


  1. 传递 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 的方式,并介绍了如何检查错误和如果传递错误。


希望本文能对你有所帮助,如果喜欢本文,可以点个关注.下一篇文章见!

宇宙古今无有穷期,一生不过须臾,当思奋争。

发布于: 2022-10-18阅读数: 29
用户头像

宇宙古今无有穷期,一生不过须臾,当思奋争 2020-05-07 加入

🏆InfoQ写作平台-签约作者 🏆 混迹于江湖,江湖却没有我的影子 热爱技术,专注于后端全栈,轻易不换岗 拒绝内卷,工作于外企开发,弹性不加班 热衷分享,执着于阅读写作,佛系不水文 同名公众号:《宇宙之一粟》

评论

发布
暂无评论
Go 语言入门很简单:Go 语言的错误处理_异常处理_宇宙之一粟_InfoQ写作社区