Golang 异常处理
文章目录
Error
- Go error 就是一个普通的接口,普通的值
|
|
-
go 选择此种方法处理异常的优势
- 简单
- 考虑失败,而不是成功
- 没有隐藏的控制流
- 完全交给开发者控制 error
- Error are values
-
异常处理注意到点
- 异常每次只应该被处理一次,即不应该调用 print 并 return
- 错误要被日志记录
- 应用程序处理错误,保证 100% 完整性,应返回完整信息
- 处理后不再报告当前错误
使用方式
直接定义 error 类型
例
- 使用 error.New 直接返回自定义异常信息
|
|
存在问题
- 很难对异常的类型进行判定,无法拦截异常类型进行特定处理
预定义的特定错误(sentinel errors)
- 提前定义异常类型信息
|
|
- 可根据异常类型进行判定,进行后续操作
|
|
存在问题
- 不够灵活,由于需要与预定义的异常类型进行等值判定,所以无法再次对异常进行封装,无法携带更多信息,否则破坏想等性检查
- 不推荐依赖 error.Error 的输出,这个方法仅用于调试或输出异常至控制台或日志,而不能将其作为字符串进行异常类型的匹配
- Sentinel errors 成为了 API 的公共部分
- 会增加包暴露的表面积,调用者在进行调用时,如果要进行异常类型的判定,那么这个异常一定是公共的,而且要有文档记录
- 如果 API 定义了一个返回特定错误的 interface,那么该接口的所有实现都将被限制为仅返回该错误,即使他们可以提供描述更具体的错误
- Sentinel errors 在两个包之间创建了依赖
- 如检查错误是否等于 io.EOF,就必须倒入 io 包
结论
- 尽可能避免 Sentinel errors
- 虽然标准库中有大量的使用,但是个人开发库时,应尽量减少避免
Error type
- 自定义一个异常类型,以包含更多的上下文信息
|
|
- 使用时可对异常类型进行断言,以针对异常进行具体的处理
- 同时又可以携带更多上下文信息
|
|
问题
- 虽然解决了 Sentinel errors 无法携带更多上下文信息的问题,但是增大了暴露包的表面积等问题仍没有解决
结论
- 尽可能避免使用,至少在公共 api 和公共包内尽量不要使用
不透明的异常处理(Opaque errors)
- 是最灵活的异常处理方式,代码和调用者之间耦合最少
- 虽然知道发生了错误,但是没有能力看到错误的内部。作为调用者,关于操作的结果,只知道他是起作用了或是没起作用
- 调用者只需要返回错误,而不假设其他内容
|
|
- 在部分情况下,二分的错误方法是不够的,在这种情况下,可以断言错误类型实现了特定的行为,而不是断言错误是特定类型或特定的值
|
|
Wrap errors
- Wrap 的方式可以包装更多的错误信息,只在最外层处理一次
- 同时又可以通过 解 Wrap 的方式获取根因,进行等值判定
- 如果和其他库进行协作,考虑使用 errors.Wrap 或者 errors.Wrapf 保存堆栈信息。同样适用 于和标准库协作的时候。
- 直接返回错误,而不是每个错误产生的地方到处打日志。
使用 golang 本身的 Wrap 功能
- go 1.13后版本支持的处理方式
- go1.13为 errors 和 fmt 标准库包引入了新特性,以简化处理包含其他错误的错误。其中最重要的是: 包含另一个错误的 error 可以实现返回底层错误的 Unwrap 方法。如果 e1.Unwrap() 返回 e2,那么我们说 e1 包装 e2,您可以展开 e1 以获得 e2。
- go1.13 errors 包包含两个用于检查错误的新函数:Is 和 As。
|
|
github.com/pkg/errors
- 能记录堆栈信息
- 在程序的顶部或者是工作的 goroutine 顶部(请求入口),使用 %+v 把堆栈详情记录
- 使用 errors.Cause 获取 root error,再进行和 sentinel error 判定
|
|
|
|
使用建议
- 只有业务代码才使用 Warp errors
- 如果定义的那个库会被很多项目依赖,则不应该使用 Warp errors,因为会记录多次堆栈信息,应只返回根因
- 果函数/方法不打算处理错误,那么用足够的上下文 Wrap errors 并将其返回到调用堆栈中
- 例如,sql 查询场景下,额外的上下文可以是使用的输入参数或失败的查询语句
- 一旦确定函数/方法处理了错误,错误就不再是错误。
- 如果函数/方法仍然需要发出返回,则它不能返回错误。它应该只返回 nil (比如降级处理中,你返回了降级数据,然后需要 return nil)。
书写技巧
错误优先判定,优先返回
|
|
方法调用函数只返回 error 时可写在一行
|
|
使用函数内置 Scan() 和 Err(),简化代码
- 需注意 return 时返回 Err(),否则会忽略掉 Scan() 一半时抛出的错误
- 包内其他方法处理操作时,先判定 sc.Err() 是否为空,如果不为空则直接 return error ,不进行任何后续操作
|
|
- 进行类结构体声明时,预留一个 err 字段,处理过程中异常写入 err,执行最后,调用 Err() 方法判断执行过程中是否有报错
|
|
参考
文章作者 Xiang
上次更新 2021-07-26