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

举报
宇宙之一粟 发表于 2022/10/27 13:49:43 2022/10/27
【摘要】 本文将介绍 Go 中的错误处理,以及为什么我们需要错误处理。什么是错误处理异常处理是任何语言都不能绕不开的话题。Go 语言没有提供传统的 try...catch 语句来处理异常,而是通过使用 error 来处理错误,用 panic 和 recover 来处理异常。错误封装是将一个错误包裹到另一个错误的过程。假设我们有一个访问数据库的 Web 服务器,并试图从数据库中获取一条记录。如果数据库调...

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


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

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

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。