Shorten repeating code with error checking [duplicate] - go

I'm currently learning go and some of my code looks like this:
a, err := doA()
if err != nil {
return nil, err
}
b, err := doB(a)
if err != nil {
return nil, err
}
c, err := doC(b)
if err != nil {
return nil, err
}
... and so on ...
This looks kinda wrong to me because the error checking takes most of the lines. Is there a better way to do error handling? Can I maybe avoid this with some refactoring?
UPDATE: Thank you for all the answers. Please note that in my example doB depends on a, doC depends on b and so on. Therefore most suggested refactorings don't work in this case. Any other suggestion?

This is a common complaint, and there are several answers to it.
Here are a few common ones:
1 - It's not so bad
This is a very common reaction to these complaints. The fact you have a few extra lines of code in your code is not in fact so bad. It's just a bit of cheap typing, and very easy to handle when on the reading side.
2 - It's actually a good thing
This is based on the fact that typing and reading these extra lines is a very good reminder that in fact your logic might escape at that point, and you have to undo any resource management that you've put in place in the lines preceding it. This is usually brought up in comparison with exceptions, which can break the flow of logic in an implicit way, forcing the developer to always have the hidden error path in mind instead. Some time ago I wrote a more in-depth rant about this here.
3 - Use panic/recover
In some specific circumstances, you may avoid some of that work by using panic with a known type, and then using recover right before your package code goes out into the world, transforming it into a proper error and returning that instead. This technique is seen most commonly to unroll recursive logic such as (un)marshalers.
I personally try hard to not abuse this too much, because I correlate more closely with points 1 and 2.
4 - Reorganize the code a bit
In some circumstances, you can reorganize the logic slightly to avoid the repetition.
As a trivial example, this:
err := doA()
if err != nil {
return err
}
err := doB()
if err != nil {
return err
}
return nil
can also be organized as:
err := doA()
if err != nil {
return err
}
return doB()
5 - Use named results
Some people use named results to strip out the err variable from the return statement. I'd recommend against doing that, though, because it saves very little, reduces the clarity of the code, and makes the logic prone to subtle issues when one or more results get defined before the bail-out return statement.
6 - Use the statement before the if condition
As Tom Wilde well reminded in the comment below, if statements in Go accept a simple statement before the condition. So you can do this:
if err := doA(); err != nil {
return err
}
This is a fine Go idiom, and used often.
In some specific cases, I prefer to avoid embedding the statement in this fashion just to make it stand on its own for clarity purposes, but this is a subtle and personal thing.

You could use named return parameters to shorten things a bit
Playground link
func doStuff() (result string, err error) {
a, err := doA()
if err != nil {
return
}
b, err := doB(a)
if err != nil {
return
}
result, err = doC(b)
if err != nil {
return
}
return
}
After you've been programming in Go a while you'll appreciate that having to check the error for every function makes you think about what it actually means if that function goes wrong and how you should be dealing with it.

If you have many of such re-occurring situations where you have several of these
error checks you may define yourself a utility function like the following:
func validError(errs ...error) error {
for i, _ := range errs {
if errs[i] != nil {
return errs[i]
}
}
return nil
}
This enables you to select one of the errors and return if there is one which
is non-nil.
Example usage (full version on play):
x, err1 := doSomething(2)
y, err2 := doSomething(3)
if e := validError(err1, err2); e != nil {
return e
}
Of course, this can be only applied if the functions do not depend on each other
but this is a general precondition of summarizing error handling.

You could create context type with result value and error.
type Type1 struct {
a int
b int
c int
err error
}
func (t *Type1) doA() {
if t.err != nil {
return
}
// do something
if err := do(); err != nil {
t.err = err
}
}
func (t *Type1) doB() {
if t.err != nil {
return
}
// do something
b, err := t.doWithA(a)
if err != nil {
t.err = err
return
}
t.b = b
}
func (t *Type1) doC() {
if t.err != nil {
return
}
// do something
c, err := do()
if err != nil {
t.err = err
return
}
t.c = c
}
func main() {
t := Type1{}
t.doA()
t.doB()
t.doC()
if t.err != nil {
// handle error in t
}
}

It looks wrong to you perhaps because you are used to not handling errors at the call site. This is quite idiomatic for go but looks like a lot of boilerplate if you aren't used to it.
It does come with some advantages though.
you have to think about what the proper way to handle this error is at the site where the error was generated.
It's easy reading the code to see every point at which the code will abort and return early.
If it really bugs you you can get creative with for loops and anonymous functions but that often gets complicated and hard to read.

You can pass an error as a function argument
func doA() (A, error) {
...
}
func doB(a A, err error) (B, error) {
...
}
c, err := doB(doA())
I've noticed some methods in the "html/template" package do this e.g.
func Must(t *Template, err error) *Template {
if err != nil {
panic(err)
}
return t
}

Related

Understanding best practices for handling returned pointers in GoLang and error checking

Let's say I have the following function
func GetNumber() (*int, error) {
numb, err := getNumberFromDB(db)
if err != nil {
return nil, errors.New("error from db")
}
return numb, nil
}
numb is an *int, but the only situation in which it ever gets returned as nil, is if there is an error
So if I'm using GetNumber(), should I also be checking to see if int is nil prior to using it? It feels like I should to prevent future changes to GetNumber from causing issues (or maybe getNumberFromDB starts returning no error for some reason at some point).
But I also feel like it's very redundant and I'm adding these conditional checks everywhere. Just want to make sure I'm not following best practices.
IE:
numb, err : = GetNumber()
if err != nil {
//handle error
}
if numb == nil {
// this shouldn't happen
}
Another Example:
func EmailFromContext(ctx context.Context, key interface{}) (*string, error) {
ctxVal, ok := ctx.Value(key).(*validator.ValidatedClaims)
if !ok {
// so should this be returning "" instead of nil?
return nil, errors.New("could not convert claims")
}
return &claims.Email, nil
}
Not knowing the implementation of getNumberFromDB(), or the underlying schema, it might be possible that a record was found, but the field in the table had a NULL value. In that case, it's possible for getNumberFromDB to return nil, nil (no number, no error). In that case, you'd still need to check for nil, even if no error was returned. I would, however, move that check to the GetNumber function, and not return pointers to integers. They're a PITA most of the time, especially when dealing with concurrency. I would write something like this:
func GetNumber() (int, error) {
i, err := getNumberFromDB()
if err != nil {
return 0, err // perhaps wrap the DB error
}
if i == nil {
return 0, ErrNumberNotSet // pre-defined error variable
}
return *i, nil // return value
}
With this function, you can just do:
i, err := GetNumber()
if err != nil {
// handle error
}
fmt.Sprintf("Got number %d from DB\n", i)
Furthermore, you can change the GetNumber function a bit more to set a default value for NULL values:
func GetNumber(def int) (int, error) {
i, err := getNumberFromDB()
if err != nil {
return 0, err // perhaps wrap the DB error
}
if i == nil {
return def, nil
}
return *i, nil // return value
}
In which case:
num, err := GetNumber(123)
if err != nil {
// handle DB error
}
fmt.Printf("Number is: %d\n", num)
In general, a non-nil error means the other results are meaningless. However, this is not always strictly followed by everyone. For example, if you're writing a gRPC server, you are not expected to return a non-nil value with a non-nil error. Whereas in the stdlib, an io.Reader returns the number of bytes read and io.EOF.
So there are several options, all of which would work:
You can assume nil error means meaningful result. In this case you have to define what meaningful result is. Based on your description, it looks like it cannot be nil, so it is OK not checking for nil.
If the function cannot return nil, then it may make sense to not return a pointer, unless there is a reason for it to be a pointer.
But then, in many situations, it may make sense to return nil,nil.
My suggestion would be to return (int,error), unless there is another reason to return a pointer. This would simplify the use of the function for the caller.

How to gracefully handle errors in web service

I am writing a simple REST API in go using gin. I have read many posts and texts about making error handling less repetitive in go, but I cannot seem to wrap my mind around how to do it in gin handlers.
All my service does is run some queries against a database and return the results as JSON, so a typical handler looks like this
func DeleteAPI(c *gin.Context) {
var db = c.MustGet("db").(*sql.DB)
query := "DELETE FROM table WHERE some condition"
tx, err := db.Begin()
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
defer tx.Rollback()
result, err := tx.Exec(query)
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
num, err := result.RowsAffected()
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
err = tx.Commit()
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{"deleted": num})
}
As you can see, even this simple handler repeats the same "if err != nil" pattern four times. In a "select" based APIs I have twice as many, since there are potential errors when binding the input data and errors when marshaling the response into JSON. Is there a good way to make this more DRY?
My normal approach is to use a wrapping function. This has the advantage (over Adrian's answer--which is also a good one, BTW) of leaving the error handling in a more Go-idiomatic form (of return result, err, as opposed to littering your code with handleError(err) type calls), while still consolidating it to one location.
func DeleteAPI(c *gin.Context) {
num, err := deleteAPI(c)
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{"deleted": num})
}
func deleteAPI(c *gin.Context) (int, error) {
var db = c.MustGet("db").(*sql.DB)
query := "DELETE FROM table WHERE some condition"
tx, err := db.Begin()
if err != nil {
return 0, err
}
defer tx.Rollback()
result, err := tx.Exec(query)
if err != nil {
return 0, err
}
num, err := result.RowsAffected()
if err != nil {
return 0, err
}
err = tx.Commit()
if err != nil {
return 0, err
}
return num, nil
}
For me (and generally, for Go coders), the priority is code readability over DRY. And of the three options (your original, Adrian's, and mine), in my opinion, my version is more readable, for the simple reason that errors are handled in an entirely idiomatic way, and they bubble to the top handler. This same approach works equally as well if your controller ends up calling other functions that return errors. By moving all error handling to the topmost function, you're free from error-handling clutter (other than the simple 'if err != nil { return err }` construct) throughout all the rest of your code.
It's also worth noting that this approach can be powerfuly combined with Adrian's, especially for use with multiple handlers, by changing the "wrapping" function as so:
func DeleteAPI(c *gin.Context) {
result, err := deleteAPI(c)
if handleError(c, err) {
return
}
c.JSON(200, gin.H{"deleted": num})
}
You can make it slightly more DRY with a helper:
func handleError(c *gin.Context, err error) bool {
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return true
}
return false
}
Used as:
err = tx.Commit()
if handleError(c,err) {
return
}
This only cuts the error handling line count from 4 lines to 3, but it does abstract away the repeated logic, allowing you to change the repeated error handling in one place instead of everywhere an error is handled (e.g. if you want to add error logging, or change the error response, etc.)

Idiomatic Go Happy Path

Suppose we have a function that returns some value and an error. What's the preferred way of handling the error and value declarations?
func example_a(data interface{}) (interface{}, error) {
var err error
var bytes []byte
if bytes, err = json.Marshal(data); err != nil {
return nil, err
}
// ...
return use(bytes), nil
}
func example_b(data interface{}) (interface{}, error) {
if bytes, err := json.Marshal(data); err != nil {
return nil, err
} else {
// ...
return use(bytes), nil
}
}
func example_c(data interface{}) (result interface{}, err error) {
var bytes []byte
if bytes, err = json.Marshal(data); err != nil {
return
}
// ...
return use(bytes), nil
}
func example_d(data interface{}) (interface{}, error) {
bytes, err := json.Marshal(data)
if err != nil {
return nil, err
}
// ...
return use(bytes), nil
}
func example_dream(data interface{}) (interface{}, error) {
if bytes, err ≡ json.Marshal(data); err != nil {
return nil, err
}
// ...
return use(bytes), nil
}
Example A is clear, but it adds 2 extra lines. Moreover, I find that it's unclear why in this particular case we should use var, and at the same time := is not always appropriate. Then you want to reuse the err declaration somewhere down the line, and I'm not a big fan of splitting declaration and assignment.
Example B is using the if-declare-test language feature, which I surmise is encouraged, but at the same time you are forced to nest function continuation violating the happy-path principle, which too is encouraged.
Example C uses the named parameter return feature, which is something between A and B. Biggest problem here, is that if your code base is using styles B and C, then it's easy to mistake := and =, which can cause all kinds of issues.
Example D (added from suggestions) has for me the same kind of usage problem as C, because inevitably I run into the following:
func example_d(a, b interface{}) (interface{}, error) {
bytes, err := json.Marshal(a)
if err != nil {
return nil, err
}
bytes, err := json.Marshal(b) //Compilation ERROR
if err != nil {
return nil, err
}
// ...
return use(bytes), nil
}
So depending on previous declarations I have to modify my code to either use := or =, which makes it harder to see and refactor.
Example Dream is what I kind of intuitively would have expected from GO - no nesting, and quick exit without too much verbosity and variable reuse. Obviously it doesn't compile.
Usually use() is inlined and repeats the pattern several times, compounding the nesting or split declaration issue.
So what's the most idiomatic way of handling such multiple returns and declarations? Is there a pattern I'm missing?
If you look at lots of Go code you will find the following to be the usual case:
func example(data interface{}) (interface{}, error) {
bytes, err := json.Marshal(data)
if err != nil {
return nil, err
}
// ...
return use(bytes), nil
}
The declare and test if construct is nice in its place, but it is not generally apropriate here.

Properly handling errors

Typically in Go you find the following convention:
res, err := thingThatCanError(arg)
if err != nil {
// handle it
}
However, it's obvious this gets VERY unruly very quickly for a large number of these calls:
res, err := thingThatCanError(arg)
if err != nil {
// handle it
}
res, err2 := thingThatCanError(arg)
if err2 != nil {
// handle it
}
res, err3 := thingThatCanError(arg)
if err3 != nil {
// handle it
}
There's more lines of boilerplate error handling than code! This website says to avoid this but does not give an example on how to clean up this smell. A useful example comes straight from the Go blog that shows us how to clean up a homogenous HTTP app with an error handler that makes sense.
But imagine each of these calls aren't homogenous, as in with the same "central idea", so a single "error handler struct" wouldn't make a lot of sense.
Is there a way to clean up this type of code smell with functions that don't "mesh together" nicely in terms of errors?
Unfortunately there's sometimes no way around these patterns. You could use panic/defer as a makeshift try/catch system but the community looks down upon it.
If statements in Go can be combined with assignments so
err := thing.Do()
if err != nil {
return err
}
can become
if err := thing.Do(); err != nil {
return err
}

Avoid checking if error is nil repetition?

I'm currently learning go and some of my code looks like this:
a, err := doA()
if err != nil {
return nil, err
}
b, err := doB(a)
if err != nil {
return nil, err
}
c, err := doC(b)
if err != nil {
return nil, err
}
... and so on ...
This looks kinda wrong to me because the error checking takes most of the lines. Is there a better way to do error handling? Can I maybe avoid this with some refactoring?
UPDATE: Thank you for all the answers. Please note that in my example doB depends on a, doC depends on b and so on. Therefore most suggested refactorings don't work in this case. Any other suggestion?
This is a common complaint, and there are several answers to it.
Here are a few common ones:
1 - It's not so bad
This is a very common reaction to these complaints. The fact you have a few extra lines of code in your code is not in fact so bad. It's just a bit of cheap typing, and very easy to handle when on the reading side.
2 - It's actually a good thing
This is based on the fact that typing and reading these extra lines is a very good reminder that in fact your logic might escape at that point, and you have to undo any resource management that you've put in place in the lines preceding it. This is usually brought up in comparison with exceptions, which can break the flow of logic in an implicit way, forcing the developer to always have the hidden error path in mind instead. Some time ago I wrote a more in-depth rant about this here.
3 - Use panic/recover
In some specific circumstances, you may avoid some of that work by using panic with a known type, and then using recover right before your package code goes out into the world, transforming it into a proper error and returning that instead. This technique is seen most commonly to unroll recursive logic such as (un)marshalers.
I personally try hard to not abuse this too much, because I correlate more closely with points 1 and 2.
4 - Reorganize the code a bit
In some circumstances, you can reorganize the logic slightly to avoid the repetition.
As a trivial example, this:
err := doA()
if err != nil {
return err
}
err := doB()
if err != nil {
return err
}
return nil
can also be organized as:
err := doA()
if err != nil {
return err
}
return doB()
5 - Use named results
Some people use named results to strip out the err variable from the return statement. I'd recommend against doing that, though, because it saves very little, reduces the clarity of the code, and makes the logic prone to subtle issues when one or more results get defined before the bail-out return statement.
6 - Use the statement before the if condition
As Tom Wilde well reminded in the comment below, if statements in Go accept a simple statement before the condition. So you can do this:
if err := doA(); err != nil {
return err
}
This is a fine Go idiom, and used often.
In some specific cases, I prefer to avoid embedding the statement in this fashion just to make it stand on its own for clarity purposes, but this is a subtle and personal thing.
You could use named return parameters to shorten things a bit
Playground link
func doStuff() (result string, err error) {
a, err := doA()
if err != nil {
return
}
b, err := doB(a)
if err != nil {
return
}
result, err = doC(b)
if err != nil {
return
}
return
}
After you've been programming in Go a while you'll appreciate that having to check the error for every function makes you think about what it actually means if that function goes wrong and how you should be dealing with it.
If you have many of such re-occurring situations where you have several of these
error checks you may define yourself a utility function like the following:
func validError(errs ...error) error {
for i, _ := range errs {
if errs[i] != nil {
return errs[i]
}
}
return nil
}
This enables you to select one of the errors and return if there is one which
is non-nil.
Example usage (full version on play):
x, err1 := doSomething(2)
y, err2 := doSomething(3)
if e := validError(err1, err2); e != nil {
return e
}
Of course, this can be only applied if the functions do not depend on each other
but this is a general precondition of summarizing error handling.
You could create context type with result value and error.
type Type1 struct {
a int
b int
c int
err error
}
func (t *Type1) doA() {
if t.err != nil {
return
}
// do something
if err := do(); err != nil {
t.err = err
}
}
func (t *Type1) doB() {
if t.err != nil {
return
}
// do something
b, err := t.doWithA(a)
if err != nil {
t.err = err
return
}
t.b = b
}
func (t *Type1) doC() {
if t.err != nil {
return
}
// do something
c, err := do()
if err != nil {
t.err = err
return
}
t.c = c
}
func main() {
t := Type1{}
t.doA()
t.doB()
t.doC()
if t.err != nil {
// handle error in t
}
}
It looks wrong to you perhaps because you are used to not handling errors at the call site. This is quite idiomatic for go but looks like a lot of boilerplate if you aren't used to it.
It does come with some advantages though.
you have to think about what the proper way to handle this error is at the site where the error was generated.
It's easy reading the code to see every point at which the code will abort and return early.
If it really bugs you you can get creative with for loops and anonymous functions but that often gets complicated and hard to read.
You can pass an error as a function argument
func doA() (A, error) {
...
}
func doB(a A, err error) (B, error) {
...
}
c, err := doB(doA())
I've noticed some methods in the "html/template" package do this e.g.
func Must(t *Template, err error) *Template {
if err != nil {
panic(err)
}
return t
}

Resources