这篇文章摘取至我在日本东京举办的 GoCon spring conference 上的演讲稿 。
文章插图
错误只是一些值我花了很多时间来思考如何在 Go 中处理错误是最好的 。我真希望能有一种简单直接的方式来处理错误,一些我们只要让 Go 程序员记住就能使用的规则,就像教数学或字母表一样 。
然而,我得到的结论是:处理错误不止有一种方式 。我认为 Go 处理错误的方式可以划分为 3 种主要的策略 。
标记错误策略第一种错误处理策略,我称之为标记错误
文章插图
这个名字来源于在实际编程中,使用一个指定的值来表示程序已经无法继续执行 。所以在 Go 中我们使用一个指定的值来表示错误 。
例如:系统包里面的 io.EOF 或是在 syscall 包中更底层些的常量错误例如
syscall.ENOENT 。甚至还有标记表示没有错误发生例如:path/filepath.Walk 中的 go/build.NoGoError 和 path/filepath.SkipDir 。
使用标记值是灵活性最差的一种错误处理策略,调用者必须使用相等操作符来比较返回值和预先定义的值 。当你想要提供更多的相关信息时,返回不同的错误值会破坏等式检查操作 。
即使通过 fmt.Errorf 来提供更多的信息也会干扰调用者的等式测试,调用者必须去看 Error 方法输出的结果是否匹配某个指定的字符串 。
永远不要检查 error.Error 的输出顺便说一下,我相信你永远都不需要检查 error.Error 方法的返回值 。error 接口中的 Error 方法是提供给使用者查看的信息,而不是用来给代码做判断的 。
这些信息应该在日志文件中或者是显示屏上出现,你不需要通过检查这些信息来改变程序行为 。
我知道有时候这样很难,就像有些人在 twitter 上提到的那样,这条建议在写测试的时候不适用 。尽管如此,在我看来,作为一种编码风格,你应该避免比较字符型的错误信息 。
标记错误成为公开 API 的一部分如果你的公开函数或方法返回了一些指定的错误值,那么这些值必须是公开的,当然也需要在文档中有所描述 。这些加入到你的 API 中了 。
如果你的 API 定义了一个返回指定错误的接口,那么所有该接口的实现都必须只返回这个错误,就算能提供更多的其他信息也不应该返回除了指定错误之外的信息 。
我们可以在 io.Reader 中看到这样的处理方式 。io.Copy 要求 reader 实现返回 io.EOF 通知调用者没有更多的数据了,但是这并不是一个错误 。
标记错误在两个包之间制造了依赖关系最大的问题是标记错误在两个包之间制造了源码层面的依赖关系 。例如:检查一个错误是否是 io.EOF 你的代码必须引入 io 包 。
这个例子看起来没那么糟糕,因为这是很普通的操作 。但是想象一下,项目中很多包导出错误值,而其他包必须导入对应的包才能检查错误条件,这样就违背了低耦合的设计原则 。
我参与过的一个大型项目,使用的就是这种错误处理模式,我可以告诉你不好的设计所带来的循环引入问题近在咫尺 。
结论:避免使用标记错误策略所以,我的建议是避免在代码中使用标记错误处理策略 。在标准库中有些情况使用了这种处理方式,但是这并不是你应该效仿的一种处理模式 。
如果有人要求你从你的包里面暴露一个错误值,你应该礼貌的拒绝他,并提供一个替代方案,也就是下面将要提到的方法 。
错误类型错误类型是我想讨论的第二种 Go 错误处理模式 。
文章插图
错误类型是你创建的实现 error 接口的类型 。在下面的例子中, MyError 类型记录了文件,行号,相关的错误信息 。
文章插图
因为 MyError 是一个类型,所以调用者可以使用 type assertion 从 error 中获取相关信息 。
文章插图
错误类型比标记错误最大的改进就是通过封装底层的错误来提供更多的相关信息 。
一个绝佳的例子就是 os.PathError 除了底层错误外还提供了使用哪个文件,执行哪个操作等相关信息 。
文章插图
错误类型存在的问题调用者可以使用 type assertion 或者 type switch,error 类型必须是公开的 。
推荐阅读
- 值得收藏!16段代码入门Python循环语句
- 微信语音视频来了,是拒接还是不接,区别真的很大
- Mysql有多少种锁,怎么写加锁的SQL语句
- 黑客入门,黑客术语名词解释 学习路线
- 茶里的十个暗语 不懂可是要吃亏
- 古城花香
- 微信新功能来了,微信加入语音消息暂停功能
- 微信支持搜索后批量删除好友,语音进度条自由拖动
- 月光花花语 月光花养殖方法
- 当我们输入一条 SQL 查询语句时,发生了什么?