go语言优雅使用技巧 go 语言( 二 )


Java 中最大的性能成本是由打印异常的堆栈跟踪造成的,这是昂贵的,因为运行的程序必须检查编译它的源代码。仅仅进入一个 try 块也不是空闲的,因为需要保存 CPU 内存寄存器的先前状态,因为它们可能需要在抛出异常的情况下恢复 。
如果您将异常视为通常不会发生的异常情况 , 那么异常的缺点并不重要 。这可能是传统的单体应用程序的情况,其中大部分代码库不必进行网络调用——一个操作格式良好的数据的函数不太可能遇到错误(除了错误的情况) 。一旦您在代码中添加 I/O,无错误代码的梦想就会破灭:您可以忽略错误,但不能假装它们不存在!
try {
doSometing()
} catch (IOException e) {
// ignore it
}
与大多数其他编程语言不同 , Golang 接受错误是不可避免的 。如果在单体架构时代还不是这样,那么在今天的模块化后端服务中 , 服务通常和外部 API 调用、数据库读取和写入以及与其他服务通信。
以上所有方法都可能失败 , 解析或验证从它们接收到的数据(通常在无模式 JSON 中)也可能失败 。Golang 使可以从这些调用返回的错误显式化 , 与普通返回值的等级相同 。从函数调用返回多个值的能力支持这一点 , 这在大多数语言中通常是不可能的 。Golang 的错误处理系统不仅仅是一种语言怪癖,它是一种将错误视为替代返回值的完全不同的方式!
重复 if err != nil
对 Go 错误处理的一个常见批评是被迫重复以下代码块:
res, err := doSomething()
if err != nil {
// Handle error
}
对于新用户来说,这可能会觉得没用而且浪费行数:在其他语言中需要 3 行的函数很可能会增长到 12 行:
这么多行代码!这么低效!如果您认为上述内容不优雅或浪费代码,您可能忽略了我们检查代码中的错误的全部原因:我们需要能够以不同的方式处理它们!对 API 或数据库的调用可能会被重试 。
有时事件的顺序很重要:调用外部 API 之前发生的错误可能不是什么大问题(因为数据从未通过发送) , 而 API 调用和写入本地数据库之间的错误可能需要立即注意,因为 这可能意味着系统最终处于不一致的状态 。即使我们只想将错误传播给调用者,我们也可能希望用失败的解释来包装它们,或者为每个错误返回一个自定义错误类型 。
并非所有错误都是相同的,并且向调用者返回适当的错误是 API 设计的重要部分 , 无论是对于内部包还是 REST API。
不必担心在你的代码中重复 if err != nil ——这就是 Go 中的代码应该看起来的样子 。
自定义错误类型和错误包装
从导出的方法返回错误时,请考虑指定自定义错误类型,而不是单独使用错误字符串 。字符串在意外代码中是可以的 , 但在导出的函数中,它们成为函数公共 API 的一部分 。更改错误字符串将是一项重大更改——如果没有明确的错误类型,需要检查返回错误类型的单元测试将不得不依赖原始字符串值!事实上,基于字符串的错误也使得在私有方法中测试不同的错误案例变得困难,因此您也应该考虑在包中使用它们 。回到错误与异常的争论,返回错误也使代码比抛出异常更容易测试 , 因为错误只是要检查的返回值 。不需要测试框架或在测试中捕获异常。
可以在 database/sql 包中找到简单自定义错误类型的一个很好的示例 。它定义了一个导出常量列表 , 表示包可以返回的错误类型 , 最著名的是 sql.ErrNoRows 。虽然从 API 设计的角度来看,这种特定的错误类型有点问题(您可能会争辩说 API 应该返回一个空结构而不是错误),但任何需要检查空行的应用程序都可以导入该常量并在代码中使用它不必担心错误消息本身会改变和破坏代码 。

推荐阅读