当 return it in go 时,我们应该如何处理错误?

How should we handle errors when return it in go?

哪个更好?

1
err := MakeFood()
if err != nil{
    return err
}
2
err := MakeFood()
if err != nil{
    logs.Errorf("failed to make food, error=%v",err)
    return err
}
3
err := MakeFood()
if err != nil{
    return fmt.Errorf("failed to make food, error=%w",err)
}
4
var ErrMakeFood = errors.New("failed to make food")

err := MakeFood()
if err != nil{
    return ErrMakeFood // we discard err
}

在我的实践中,return fmt.Errorf("xxx, error=%w",err) 是我最喜欢的,它会在发生错误时创建级联错误字符串 return.
但似乎,在 go builtin src 代码中,return err 是正常和整洁的。
有时我们建议使用静态错误声明(我给出的示例 4)。这是名为 goerr113 的 golang-lint 的 lint 规则。

没有一个更好,它们都是不同的,可以适用于不同的情况。

第一个,

err := MakeFood()
if err != nil{
    return err
}

当已知 MakeFood 为它 returns 的所有错误提供自己的上下文时,

是可以的(或包装和 returns —“冒泡”);例如,任何错误 returns 都包含在“failed to make food:”上下文中。

它也适用于不需要提供任何额外上下文的其他情况。

第二个和第三个适用于执行一些概念上单一(原子)任务的更大功能的上下文,该任务由多个步骤组成(制作食物就是其中一个步骤);在这种情况下,通常会将每个步骤报告的错误包装在同一上下文中——当调用链中的每个调用都添加自己的上下文时,会导致上下文“链接”。 例如,如果我们更大的函数处理一个外卖订单,它可能看起来像这样:

func ProcessFoodDeliveryOrder() error {
  meal, err := MakeFood()
  if err != nil {
    return fmt.Errorf("failed to process order: %w", err)
  }
  err := Deliver(meal, address)
  if err != nil {
    return fmt.Errorf("failed to process order: %w", err)
  }
  return nil
}

关于是否应该使用较新的 Go 工具的问题,即通过使用 %w 格式化动词来实际 嵌套 错误,而不是仅提供文本(消息)上下文——是一个开放的:不是所有的错误都需要被包装(然后由 errors.Is 测试或由 errors.As 处理);没有必要仅仅因为你知道它存在并且看起来很酷就使用它:它有它的运行成本。

错误消息最好使用普通的 : %s 组合来格式化——就像 fmt.Errorf("new context: %s", err) 一样,因为以这种方式改变错误会产生一个简单易懂且易于阅读的“action: reason”文本。顺便说一句,在 The Book.

中推荐

后一种方法称为“哨兵错误”。可以使用 如果 你的包的用户实际上 使用 这样的错误变量——直接或通过使用 errors.Is 测试它。
但也要考虑断言错误的行为可能更好,而不是将它们与哨兵变量进行比较。考虑阅读 this and look at net.Error.