17 错误处理

// 我觉得这个处理挺好的
if err != nil {
    return err
}

0.1. 好处

如果以标准方式处理 Go 中的错误,将获得以下好处:

  1. 没有隐藏的控制流
  2. 没有意外的未捕获异常日志在终端疯狂输出(除了由于 panic 导致的实际程序崩溃)
  3. 可以完全控制代码中的错误,可以选择处理,返回和执行任何其他操作

func f() (value, error) 的语法不仅易于向新手讲解,而且在任何 Go 项目中都可确保一致性。

注意:Go 的错误语法不会强迫处理程序可能抛出的每个错误。

Go 只是提供了一种模式,以确保你认为错误对于程序流至关重要,没有其他更多要求。

  • 应用程序结束时,如果发生错误,并且使用 err != nil 来发现它
  • 如果应用程序不对其执行任何操作,则可能会遇到麻烦(Go 不会自动保存任何信息)
if err := criticalDatabaseOperation(); err != nil {
    // 仅仅是记录错误日志,而没有返回或停止控制流(这样不好!)
    log.Printf("Something went wrong in the DB: %v", err)
    // 在这一行我们应该输入`return`!
}

if err := saveUser(user); err != nil {
    return fmt.Errorf("Could not save user: %w", err)
}

0.2. 最佳实践

0.2.1. 创建可行的错误链

if err != nil 模式的优势在于,通过错误链能方便遍历程序层次结构,直到发生错误的地方。

例如,由程序的 main 函数处理的常见 Go 错误可能如下所示:

[2020-07-05-9:00] ERROR: Could not create user: could not check if user already exists in DB: could not establish database connection: no internet connection

:也能看出,这是四个函数调用发错误返回,可以快速定位到no internet connection导致了这个错误的发生。

0.2.2. 附加堆栈跟踪信息

github.com/pkg/errors提供了将堆栈跟踪附加到函数的功能。

0.2.2.1. 向错误添加上下文信息

errors.Wrap函数返回一个新错误,这个新错误通过在调用Wrap的点记录堆栈跟踪以及所提供的消息来将上下文添加到原始错误中。例如:

_, err := ioutil.ReadAll(r)
if err != nil {
        return errors.Wrap(err, "read failed")
}

它将打印出堆栈跟踪以及您通过代码创建的易于理解的错误链。如果可以,我想总结一下我想到的有关在 Go 中编写符合

0.3. 建议

Go 习惯的错误处理的最重要建议:

  1. 当您的错误需要服务开发人员时,请添加堆栈跟踪
  2. 对返回的错误进行处理,不要只是将它们冒出来(返回),记录下来,然后忘记它们
  3. 保持您的错误链明确

当编写 Go 代码时,错误处理是永远不会担心的一件事,因为错误本身是编写的每个函数的核心内容之一,从而使我能够完全控制我如何安全,可读且负责任地处理它们。

上次修改: 16 July 2020