可接受的 Golang 惯用嵌套错误处理方式?

9

最近我开始学习Go语言并看到了很多关于如何进行错误处理的讨论。

我所看到的模式如下:

err := DoSomething()
if err != nil {
   //handle
}
// continue

在管理AMQP连接时,我通常的情况是只有在错误为零时才需要继续进行操作,因为此时我需要在连接上做些事情。

c, err := Connect()
if err != nil {
   return nil, err
}
s,err := c.RegisterSomethingOnConnection()
if err != nil {
   return nil, err
}
val, err := s.DoSomething()
return val, err

如您所见,我只想在从Connect()返回错误值为nil时运行c.RegisterSomethingOnConnection这一行代码。

然而,我不喜欢上述写法,因为它使用了早期返回。早期返回会降低代码可读性,并使函数退出时间变得不清晰。目前,我的解决方案是:

var err error
var val ReturnType

c,err := Connect()
if err == nil {
    s,err := c.RegisterSomethingOnConnection()
    if err == nil {
       val,err = s.DoSomething()
    }
}
return val,err

我喜欢这样做有两个原因。首先,它可以避免返回 nil。其次,我发现这使得代码更易于维护,因为您可以在返回之前轻松添加功能(例如日志记录),并且不会因为早期返回而导致某些路径错过了添加的功能。

我的做法是否符合惯用的 Go 语言规范,或者我只是需要克服我对早期返回的厌恶,并遵循该模式?


2个回答

7

Go语言中的一个Go Prover是:

不只是检查错误,要优雅地处理它们

我建议您阅读Dave Cheney在此篇文章中的观点。

以下是文章中的重点:

"没有一种单一的方法来处理错误。相反,我认为Go的错误处理可以分为三种核心策略"

  • 标志性错误:

if err == ErrSomething { … }

"使用标志值是最不灵活的错误处理策略,因为调用者必须使用等号运算符将结果与预先声明的值进行比较。当您想提供更多上下文时,这会出现问题,因为返回不同的错误会打破等式检查。"

  • 错误类型

if err, ok := err.(SomeType); ok { … }

"错误类型是您创建的实现错误接口的类型。"

  • 不透明错误

x, err := bar.Foo() if err != nil { return err } // use x

"我将这种风格称为不透明的错误处理,因为虽然您知道发生了错误,但您无法查看错误的内部。作为调用者,您对操作结果的所有了解就是它是否成功。"

.... 阅读整篇文章。

我认为错误处理的重要方面是不只是检查错误,要优雅地处理它们,希望这能帮到您。


谢谢,这篇文章非常有帮助。如果我理解正确的话,如果您已经将其包装在其他上下文中并仅在一个地方记录了日志,则返回错误是可以的,对吗? - Fuzzerker
@Fuzzerker 我认为是正确的,这些错误是人为的而不是代码的。 - Eddy Hernandez
@Fuzzerker 我喜欢这种方式,return fmt.Errorf("authenticate failed: %v", err) 不要错过错误发生的位置。 - Eddy Hernandez

0

处理来自多个抽象级别的错误的最佳实践

你应该要么处理错误,要么不处理但将其委托给更高层(调用者)处理。 处理错误并返回它是不好的做法,因为如果调用者也这样做,错误可能会被处理多次。

另请参见:
错误是值 by Rob Pike.

Go 错误处理技术


你可以简化它:

if err := datastore.Get(c, key, record); err != nil {

简化重复的错误处理:

In Go, error handling is important. The language's design and conventions encourage you to explicitly check for errors where they occur (as distinct from the convention in other languages of throwing exceptions and sometimes catching them). In some cases this makes Go code verbose, but fortunately there are some techniques you can use to minimize repetitive error handling.

Consider an App Engine application with an HTTP handler that retrieves a record from the datastore and formats it with a template.

func init() {
    http.HandleFunc("/view", viewRecord)
}

func viewRecord(w http.ResponseWriter, r *http.Request) {
    c := appengine.NewContext(r)
    key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil)
    record := new(Record)
    if err := datastore.Get(c, key, record); err != nil {
        http.Error(w, err.Error(), 500)
        return
    }
    if err := viewTemplate.Execute(w, record); err != nil {
        http.Error(w, err.Error(), 500)
    }
}

请参见:https://blog.golang.org/error-handling-and-go


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接