1. error 接口定义

除用 panic 引发中断性错误外,还可返回 error 类型错误对象来表示函数调用状态。error 接口是 Go 原生内置的类型,它的定义如下:

// $GOROOT/src/builtin/builtin.go
type interface error {Error() string
}

在这个接口类型的声明中只包含了一个方法 ErrorError 方法不接受任何参数,但是会返回一个 string 类型的结果。它的作用是返回错误信息的字符串表示形式。

任何实现了 errorError 方法的类型的实例,都可以作为错误值赋值给 error 接口变量。

一般情况下在 Go 里只使用 error 类型判断错误, Go 官方希望开发者能够很清楚的掌控所有的异常,在每一个可能出现异常的地方都返回或判断 error 是否存在。

标准库 errors.Newfmt.Errorf 函数用于创建实现 error 接口的错误对象。通过判断错误对象实例来确定具体错误类型。

err := errors.New("your first demo error")
errWithCtx = fmt.Errorf("index %d is out of bounds", i)

这两种方法实际上返回的是同一个实现了 error 接口的类型的实例,这个未导出的类型就是errors.errorString,它的定义是这样的:

// $GOROOT/src/errors/errors.go
type errorString struct {s string
}func (e *errorString) Error() string {return e.s
}

2. 创建 error 接口对象

2.1 errors.New 创建 error 接口错误对象

在生成 error 类型值的时候,用到了 errors.New 函数。

这是一种最基本的生成错误值的方式。我们调用它的时候传入一个由字符串代表的错误信息,它会给返回给我们一个包含了这个错误信息的 error 类型值。

该值的静态类型当然是 error ,而动态类型则是一个在 errors 包中的,包级私有的类型 *errorString

package mainimport "errors"var ErrDivByZero = errors.New("division by zero")func div(x, y int) (int, error) {if y == 0 {return 0, ErrDivByZero}return x / y, nil
}
func main() {switch z, err := div(10, 0); err {case nil:println(z)case ErrDivByZero:panic(err)}
}

输出结果:

panic: division by zerogoroutine 1 [running]:
main.main()/home/wohu/gocode/src/hello.go:18 +0xa7
exit status 2

2.2 fmt.Errorf 创建 error 接口错误对象

我们已经知道,通过调用 fmt.Printf 函数,并给定占位符 %s 就可以打印出某个值的字符串表示形式。对于其他类型的值来说,只要我们能为这个类型编写一个 String 方法,就可以自定义它的字符串表示形式。

而对于 error 类型值,它的字符串表示形式则取决于它的 Error 方法。在上述情况下,fmt.Printf 函数如果发现被打印的值是一个 error 类型的值,那么就会去调用它的 Error 方法。fmt 包中的这类打印函数其实都是这么做的。

顺便提一句,当我们想通过模板化的方式生成错误信息,并得到错误值时,可以使用 fmt.Errorf 函数。该函数所做的其实就是先调用 fmt.Sprintf 函数,得到确切的错误信息;再调用 errors.New 函数,得到包含该错误信息的 error 类型值,最后返回该值。

package mainimport ("errors""fmt"
)func main() {err1 := fmt.Errorf("invalid contents: %s", "#$%")err2 := errors.New(fmt.Sprintf("invalid contents: %s", "#$%"))if err1.Error() == err2.Error() {fmt.Println("The error messages in err1 and err2 are the same.")}
}

输出结果:

The error messages in err1 and err2 are the same.

fmt.Errorf 函数 示例:

package mainimport "fmt"func foo(i int, j int) (r int, err error) {if j == 0 {err = fmt.Errorf("参数 2 不能为 %d", j) //给 err 变量赋值一个 error 对象return //返回 r 和 err,因为定义了返回值变量名,所以不需要在这里写返回变量}return i / j, err //如果没有赋值 error 给 err 变量,err 是 nil
}func main() {//传递 add 函数和两个数字,计算相加结果n, err := foo(100, 0)if err != nil { //判断返回的 err 变量是否为 nil,如果不是,说明函数调用出错,打印错误内容println(err.Error())} else {println(n)}
}

3. 可导出哨兵错误值方式

Go 标准库采用了定义导出的“哨兵”错误值的方式,来辅助错误处理方检视错误值并做出错误处理分支的决策,比如下面的 bufio 包中定义的“哨兵错误”:

// $GOROOT/src/bufio/bufio.go
var (ErrInvalidUnreadByte = errors.New("bufio: invalid use of UnreadByte")ErrInvalidUnreadRune = errors.New("bufio: invalid use of UnreadRune")ErrBufferFull        = errors.New("bufio: buffer full")ErrNegativeCount     = errors.New("bufio: negative count")
)

下面的代码片段利用了上面的哨兵错误,进行错误处理分支的决策:

data, err := b.Peek(1)
if err != nil {switch err {case bufio.ErrNegativeCount:// ... ...returncase bufio.ErrBufferFull:// ... ...returncase bufio.ErrInvalidUnreadByte:// ... ...returndefault:// ... ...return}
}

一般“哨兵”错误值变量以 ErrXXX 格式命名。
Go 1.13 版本开始,标准库 errors 包提供了 Is 函数用于错误处理方对错误值的检视。Is 函数类似于把一个 error 类型变量与“哨兵”错误值进行比较,比如下面代码:

// 类似 if err == ErrOutOfBounds{ … }
if errors.Is(err, ErrOutOfBounds) {// 越界的错误处理
}

不同的是,如果 error 类型变量的底层错误值是一个包装错误(Wrapped Error),errors.Is 方法会沿着该包装错误所在错误链(Error Chain),与链上所有被包装的错误(Wrapped Error)进行比较,直至找到一个匹配的错误为止。下面是 Is 函数应用的一个例子:

var ErrSentinel = errors.New("the underlying sentinel error")func main() {err1 := fmt.Errorf("wrap sentinel: %w", ErrSentinel)err2 := fmt.Errorf("wrap err1: %w", err1)println(err2 == ErrSentinel) //falseif errors.Is(err2, ErrSentinel) {println("err2 is ErrSentinel")return}println("err2 is not ErrSentinel")
}

在这个例子中,我们通过 fmt.Errorf 函数,并且使用 %w 创建包装错误变量 err1err2,其中 err1 实现了对 ErrSentinel 这个“哨兵错误值”的包装,而 err2 又对 err1 进行了包装,这样就形成了一条错误链。位于错误链最上层的是 err2,位于最底层的是 ErrSentinel。之后,我们再分别通过值比较和 errors.Is这两种方法,判断 err2ErrSentinel 的关系。运行上述代码,我们会看到如下结果:

false
err2 is ErrSentinel

我们看到,通过比较操作符对 err2ErrSentinel 进行比较后,我们发现这二者并不相同。而 errors.Is 函数则会沿着 err2 所在错误链,向下找到被包装到最底层的“哨兵”错误值 ErrSentinel

所以,如果你使用的是 Go 1.13 及后续版本,我建议你尽量使用 errors.Is 方法去检视某个错误值是否就是某个预期错误值,或者包装了某个特定的“哨兵”错误值。

4. 错误值类型检视策略

上面基于 Go 标准库提供的错误值构造方法构造的“哨兵”错误值,除了让错误处理方可以“有的放矢”的进行值比较之外,并没有提供其他有效的错误上下文信息。那如果遇到错误处理方需要错误值提供更多的“错误上下文”的情况,上面这些错误处理策略和错误值构造方式都无法满足。

这种情况下,我们需要通过自定义错误类型的构造错误值的方式,来提供更多的“错误上下文”信息。并且,由于错误值都通过 error 接口变量统一呈现,要得到底层错误类型携带的错误上下文信息,错误处理方需要使用 Go 提供的类型断言机制(Type Assertion)或类型选择机制(Type Switch),这种错误处理方式,我称之为错误值类型检视策略。

Go 1.13 版本开始,标准库 errors 包提供了As 函数给错误处理方检视错误值。As 函数类似于通过类型断言判断一个 error 类型变量是否为特定的自定义错误类型,如下面代码所示:

// 类似 if e, ok := err.(*MyError); ok { … }
var e *MyError
if errors.As(err, &e) {// 如果err类型为*MyError,变量e将被设置为对应的错误值
}

不同的是,如果 error 类型变量的动态错误值是一个包装错误,errors.As 函数会沿着该包装错误所在错误链,与链上所有被包装的错误的类型进行比较,直至找到一个匹配的错误类型,就像 errors.Is 函数那样。下面是 As 函数应用的一个例子:

type MyError struct {e string
}func (e *MyError) Error() string {return e.e
}func main() {var err = &MyError{"MyError error demo"}err1 := fmt.Errorf("wrap err: %w", err)err2 := fmt.Errorf("wrap err1: %w", err1)var e *MyErrorif errors.As(err2, &e) {println("MyError is on the chain of err2")println(e == err)                  return                             }                                      println("MyError is not on the chain of err2")
}

运行上述代码会得到:

MyError is on the chain of err2
true

我们看到,errors.As 函数沿着 err2 所在错误链向下找到了被包装到最深处的错误值,并将 err2 与其类型 * MyError 成功匹配。匹配成功后,errors.As 会将匹配到的错误值存储到 As 函数的第二个参数中,这也是为什么 println(e == err) 输出 true 的原因。

所以,如果你使用的是 Go 1.13 及后续版本,请尽量使用 errors.As 方法去检视某个错误值是否是某自定义错误类型的实例。

Go 学习笔记(64)— Go error.New 创建接口错误对象、fmt.Errorf 创建接口错误对象、errors.Is 和 errors.As相关推荐

  1. Linux从头开始学--学习笔记9知识点补充-ubuntu,centos;在linux上创建c程序;linux基础命令,shell命令,vi命令,man帮助手册

    这是我从头开始学习Linux的学习笔记,后续还会更新. 记录自己的技术成长,也希望和大家分享交流,欢迎关注~ 本笔记为coursera网站课程<Linux for Developers>的 ...

  2. 【极客学院】-python学习笔记-Python快速入门(面向对象-引入外部文件-Web2Py创建网站)

    极客学院的课程,感觉很有意思,每节课都很短,但是很干货,我喜欢这个节奏 http://www.jikexueyuan.com/course/203.html 课程背景: Python语言功能强大, 能 ...

  3. Vue学习笔记(三)Vue2三种slot插槽的概念与运用 | ES6 对象的解构赋值 | 基于Vue2使用axios发送请求实现GitHub案例 | 浏览器跨域问题与解决

    文章目录 一.参考资料 二.运行环境 三.Vue2插槽 3.1 默认插槽 3.2 具名插槽 3.3 作用域插槽 ES6解构赋值概念 & 作用域插槽的解构赋值 3.4 动态插槽名 四.GitHu ...

  4. Linux shell 学习笔记(11)— 理解输入和输出(标准输入、输出、错误以及临时重定向和永久重定向)

    1. 理解输入和输出 1.1 标准文件描述符 Linux 系统将每个对象当作文件处理.这包括输入和输出进程.Linux 用文件描述符(file descriptor)来标识每个文件对象.文件描述符是一 ...

  5. python学习笔记(六)——类的初始化(__init__)、类属性和类方法 和 对象

    学习本篇文章后会了解到:类的创建,为对象添加属性,对象的初始化,自定义对象的输出,类属性和类方法的创建. 1. 类的定义与格式 类是对一群具有相同特征或者行为的事物的一个统称. 类是一个模块,是负责创 ...

  6. Java面向对象基础学习笔记(构造、重载、继承、多态、抽象类、接口、模块)

    Java面向对象编程包含哪些内容? 怎么理解面向对象编程? 现实生活中,我们定义了"人"的抽象概念,这就是类class,生活中的每一个具体的人就是实例instance. class ...

  7. oracle学习日志---返回RemoteOperationException: ERROR: Wrong password for user-错误的用户名密码-的错误的解决办法...

    ERROR Wrong password for user 在OEM中有些操作需要输入操作系统的用户名密码才能继续下去,但是以前无论怎样输入,总是会返回RemoteOperationException ...

  8. unity学习笔记-番外(接入百度和轻语的AI智能接口实现语音识别和语音播放)

    接入百度和轻语的AI智能接口实现语音识别和语音播放 语音识别 思路 代码 语音合成 思路 总结 语音识别 思路 先在百度和轻语申请接口,获得appkey和secretkey(这是为了获得鉴权,也就是t ...

  9. 深入浅出mfc学习笔记——六大关键技术之仿真_运行时和动态创建

    1:PS88:MFC的类层次结构 <1>CObject <2>CCmdTarget,CDocument <3>CCmdTarget_CWinThread_CWinA ...

最新文章

  1. 第四周项目四-程序分析(1)
  2. MySql各引擎特点和性能测试
  3. 语言设计谁年龄大_这桌子谁设计的?脑洞够大,除能360°翻转,打台球,乒乓球都行...
  4. 钮扣电池电压电量_纽扣厂
  5. java如何实取随机数_java - 如何在Kotlin中获取随机数?
  6. 【git】gitk 通过图形界面工具来查看版本历史
  7. 中国如何在 AI 芯片实现弯道超车?
  8. java 广告插件_徒手创建一个chrome扩展-屏蔽广告插件
  9. 专访UCloud徐亮:UCloud虚拟网络的演进之路
  10. react 加粗_css字体如何加粗?
  11. 3D打印技术到底有多强大?
  12. 能打开网页 玩游戏找不到服务器,浏览器打不开网页但可以玩游戏上QQ?原是DNS在作怪...
  13. 论劳动生产力进步的原因,兼论劳动产品在不同阶级人民之间自然分配顺序(读后感)
  14. 华为云nbiot接入示例_nbiot
  15. JSP+MYSQL网上作业提交及管理系统
  16. [附源码]Python计算机毕业设计Django的项目管理系统
  17. 用起这 16 个顶尖 Vue 开源项目,节约更多的时间摸鱼
  18. <Python的输入、输出、注释>——《Python》
  19. 最长对称字符串php_PHP-字符串过长不用担心
  20. 【OpenCV入门教程之十二】OpenCV边缘检测:Canny算子,Sobel算子,Laplace算子,Scharr滤波器合辑

热门文章

  1. 科学处理java.lang.StackOverflowError: null异常
  2. Docker 入门系列(6)- Docker 互联
  3. 网络安全工具:Nmap
  4. 第四天:Vue组件的slot以及webpack
  5. GOF23设计模式(创建型模式)单例模式
  6. 什么是L1/L2/L3 Cache?
  7. 可视化反投射:坍塌尺寸的概率恢复:ICCV9论文解读
  8. Cookie和Session的区别与联系
  9. Activity在有Dialog时按Home键的生命周期
  10. 2021年大数据Spark(九):Spark On Yarn两种模式总结