Go语言error的设计理念及背景演化详解
bluesGavin 人气:0背景
作为一门相对新兴的语言,Go 可以说是站在巨人的肩膀上。从 Go 语法上,我们可以看出设计者对其有许多严肃的思考。其中 Error 的处理就是极具标志性的一项。
值得注意的是,Go 中的 Error 一直是一个有争议的内容。很多议案不断的提出又推翻,即便是官方的 error 库也在多个版本中不断迭代。本文内容基于 1.19.3 版本的 Go 编写。
各语言中 Error 的演化
想要了解 Error 在 Go 中占据话题的原因,我们需要先了解 Error 在编程语言中的演化。
C语言
C语言中开发者需要根据函数的返回值作错误判断。每个函数是单返回值,一般通过传递指针作为入参。返回值为 int 类型,表示成功或失败。
显然在这个阶段,语言设计者只单纯地把错误考虑为“异常”情况,由开发者判断函数的执行结果。这种情况下,错误中并没有太多信息。这会加重开发者对错误的处理工作。
C++
进入C++时代以后,语言带来了 exception 。错误会作为一个程序正常执行以外的一个特殊情况被抛出。而开发者可以通过 try....catch.... 对异常作特殊处理。
这种设计模式的影响很广,包括 Javascript 在内很多语言都延续了这种设计。其特点为,开发者可以知道错误在哪里抛出,但不能具体知道调用方会抛出什么异常。
Go 中 Error 的理念
在 Go 的 Error 中,我们可以看到两个 ERROR 的特点。
1. 区分 Error 和 Exception
第一点是,Go 中真正从语言设计上区分开了 Error 和 Exception。Go 的处理异常逻辑不引入 exception ,支持多参数返回,所以开发者可以很容易在函数签名中带上实现了 error interface 的对象,交由调用者处理。
通常如果一个函数返回了 value 和 error , 开发者需要先判定 error,再利用处理 value 处理下一步逻辑。常见代码如下:
value, err := getSomething() if err != nil{ // 处理错误 return } // 逻辑处理
另外,Go 中引入了 panic 机制,它与其他语言的 exception 不完全一样。在其他语言中,当程序抛出异常时,相当于把 exception 抛给开发者处理。而 Go 中的 panic 是专门,针对真正意外,不可恢复的情况,如索引越界、不可恢复的环境问题、栈溢出。
同时,GO 也提供了从 panic 中恢复的接口—— recover 。但这不意味着开发者应该把其当作 try... catch... 使用。而是应该当作是程序崩溃后的特殊处理的最后机会。在 Go 的设计中,panic 一旦触发,说明程序应该要退出了。但在某些业务场景下,我们可能还会有日志上报,日志打印,信息通知等操作。此时,就应该考虑使用 recover。
2.Error是一个接口
第二点是,Error 在 Go 中其实是一个普通的接口。它不仅保存着错误的信息,还提供了一系列的方式供开发者使用。因此开发者可以自行拓展,嵌套,封装新的 error ,为项目提供自定义错误模块。
由此看来,Error 在 Go 中并不像其他语言一样,是一个特殊的类型。它只是一个普通的值,开发者完全可以自己实现一套新的 Error 接口。但更多时候,官方的 errors 库已经可以覆盖绝大多数场景了。
因此, Go 的设计并不是在定义一套新的 Error 机制。而是在制定一套 Error 在代码逻辑中处理的规范。你不必完全遵守这套规范,但在各种实际开发经验总结来看,Go 的这种设计模式确实赢得了很多开发者的青睐。
示例代码:
// 创建一个error newErr := errors.New("一个错误") // 判断error类型 if errors.Is(newErr, fs.ErrNotExist) { fmt.Println("file does not exist") } else { fmt.Println(err) }
总结
Go 之所以从诞生至今,吸引了这么多的粉丝。处理语言的机制以外,更多的就是它的设计理念。而本文讲述的 error 就是其中一部分。我们在使用 Go 开发时,除了按照它推荐的规范编码至于,还应该关心、理解它的设计。只有这样,才能写出更优秀的作品。
进一步阅读 官方 errors 库文档地址:pkg.go.dev/errors
加载全部内容