6
\$\begingroup\$

I have a simple function:

func getList() (sendgrid.RecipientList, bool, agerr.E) {
    lists, err := client.GetRecipientLists()
    if err == nil {
        for _, l := range lists {
            if l.Name == myList {
                return l, true, nil
            }
        }
    }
    return sg.RecipientList{}, false, err
}

but my colleagues ask my to rewrite it into:

func getList() (sendgrid.RecipientList, bool, agerr.E) {
    lists, err := client.GetRecipientLists()
    if err != nil {
        return sg.RecipientList{}, false, err
    }
    for _, l := range lists {
        if l.Name == myList {
            return l, true, nil
        }
    }
    return sg.RecipientList{}, false, err
}

They argue that errors should be returned as fast as possible. Is it really so big problem? What is your opinion?

\$\endgroup\$
0

1 Answer 1

3
\$\begingroup\$

There's a couple of reasons if err != nil is usually preferred in Go.

It's idiomatic

If you take a look at the source code of Go's standard library you can find it plenty of times, like in bufio's source, for example.

It's more readable in the long run

In your function, the difference between the two is not that big, but the point is valid if you're writing longer functions, where you make multiple calls to other functions that might return errors.

Suppose your function had to call 3 other "failable" functions, handling errors like in the first snippet would look like this:

func getList() (sendgrid.RecipientList, bool, agerr.E) {
    lists, err := client.GetRecipientLists()
    if err == nil {
        // let's call it again, just for fun
        lists, err = client.GetRecipientLists()
        if err == nil {
            // once more!
            lists, err = client.GetRecipientLists()
            if err == nil {
                 // function logic goes here
            }
        }
    }

    return sg.RecipientList{}, false, err
}

You can probably see that lines get much longer, and in this case there's even no actual logic in the snippet I wrote. IMO this reduces readability by a lot, it's harder to separate an error check from function logic at a glance.

Let's try with the other style:

func getList() (sendgrid.RecipientList, bool, agerr.E) {
    lists, err := client.GetRecipientLists()
    if err != nil {
        return sg.RecipientList{}, false, err
    }       

    // let's call it again, just for fun
    lists, err = client.GetRecipientLists()
    if err != nil {
        return sg.RecipientList{}, false, err
    }   

    // once more!
    lists, err = client.GetRecipientLists()
    if err != nil {
        return sg.RecipientList{}, false, err
    }        

    // function logic goes here

    return sg.RecipientList{}, false, err
}

Of course, inverting the condition means that you have to type more returns and you're not really saved from writing all that error handling boilerplate.

At the same time, you can probably notice it's much easier to mentally filter out the actual function logic from the boilerplate. Every check is stand-alone and always looks similar.

As a side note, if you don't like all that boilerplate there's an interesting article on the Go blog that discusses of possible ways to reduce it.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.