goidioms

Acceptable Golang idiomatic nested error handling?


I have recently gotten into Go and have seen a lot of discussion about how to do error handling.

the pattern that I have seen laid out is the following:

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

often times when managing amqp connections, my condition is that I want to continue only if the error is nil, because then I need to do something on the connection:

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

as you can see I only want to run the line c.RegisterSomethingOnConnection if the error returned from Connect() is nil.

However, I dislike the above due to the early returns. Early returns make me uncomfortable because in the long run it hurts readability and obscures exactly when a function exits. My solution so far has been to do the following:

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

I like to do this for 2 reasons. First, it prevents returning nil. Second, I find it makes the code more maintainable as you can add easily add functionality before returning (i.e. logging) and not have certain paths miss the added functionality due to an early return.

Is what I have done acceptable idiomatic Go or do I just need to get over my dislike of early returns and follow the that pattern?


Solution

  • One of the Go Prover is:

    Don’t just check errors, handle them gracefully

    I recommend you to read this post from Dave Cheney

    I put the highlights here:

    "There is no single way to handle errors. Instead, I believe Go’s error handling can be classified into the three core strategies"

    if err == ErrSomething { … }

    "Using sentinel values is the least flexible error handling strategy, as the caller must compare the result to predeclared value using the equality operator. This presents a problem when you want to provide more context, as returning a different error would will break the equality check."

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

    "An error type is a type that you create that implements the error interface. "

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

    "I call this style opaque error handling, because while you know an error occurred, you don’t have the ability to see inside the error. As the caller, all you know about the result of the operation is that it worked, or it didn’t."

    .... read all the post.

    I think the important aspect of error handling is to Don’t just check errors, handle them gracefully, I hope this can help you.