Properly handling errors - go

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
}

Related

Is there a good way to do cache saves in goroutine?

Let's say I have a handler that makes a request and gets the latest data on the selected stock:
func (ss *stockService) GetStockInfo(ctx *gin.Context) {
code := ctx.Param("symbol")
ss.logger.Info("code", code)
url := fmt.Sprintf("URL/%v", code)
ss.logger.Info(url)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
errs.HTTPErrorResponse(ctx, &ss.logger, errs.New(errs.Internal, err))
return
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
errs.HTTPErrorResponse(ctx, &ss.logger, errs.New(errs.Internal, err))
return
}
defer resp.Body.Close()
var chart ChartResponse
err = json.NewDecoder(resp.Body).Decode(&chart)
if err != nil {
errs.HTTPErrorResponse(ctx, &ss.logger, errs.New(errs.Internal, err))
return
}
ctx.JSON(http.StatusOK, chart)
}
And I want to add caching here. Since I don't have a lot of experience right now, I'm interested in proper interaction with the cache.
I think that if, for example, it is not possible to save to the cache for some reason, then you can simply make a request to the api. Then I wonder if it would be right to save to the cache in a separate goroutine and immediately return the response:
func (ss *stockService) GetStockInfo(ctx *gin.Context) {
code := ctx.Param("symbol")
stockInfo, err := ss.cache.Get(code)
if err == nil {
// FIND
...
ctx.JSON(http.StatusOK, chart)
} else {
ss.logger.Info("code", code)
url := fmt.Sprintf("URL/%v", code)
ss.logger.Info(url)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
...
err = json.NewDecoder(resp.Body).Decode(&chart)
// IS IT A GOOD WAY ?
go ss.cache.Save(code,chart,expireAt)
ctx.JSON(http.StatusOK, chart)
}
}
I use redis as a cache.
I will be glad if someone says what is wrong with this approach.

Shorten repeating code with error checking [duplicate]

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
}

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.)

Should I error-check Close() on a response body?

The docs for net/http have the following example:
resp, err := http.Get("http://example.com/")
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
fmt.Printf("%s", body)
Close returns an error, but it is not checked. Is there something I'm missing here? The importance of checking every error is frequently emphasized in go, but I see this defer resp.Body.Close() pattern a lot with no error checks.
There are two things to consider: What would you do with it if you checked it and there was an error? And, what would the side-effects be if there was an error?
In most cases, for closing a response body, the answer to both questions is... absolutely nothing. If there's nothing you'd do if there was an error and the error has no appreciable impact, there's no reason to check it.
Also note that Close() returns an error in order to fulfill the io.Closer interface; the function doesn't necessarily return an error. You'd need to check the source to know for sure if it has an error case.
This is a downside of using defer
It would be advised, as a responsible developer, that you check for all possible error prone points, and handle them as gracefully as you can.
Here are some of the options you can choose in handling this situation:
Option #1
Do not use defer, instead manually call close once you're done with the response's body and simply check for errors then.
Option #2
Create an anonymous function that wraps the closing and error checking code. Something like this:
defer func() {
err := resp.Body.Close()
if err != nil {
log.Fatal(err)
}
}()
Avoid using panics in your programs. Try to handle the errors gracefully by doing something or at least logging the error.
Additional information can be found here.
To add to #Mihailo option #2, call it option #2.1
Define function dclose() like so:
func dclose(c io.Closer) {
if err := c.Close(); err != nil {
log.Fatal(err)
}
}
use like so:
defer dclose(resp.Body)
Also in your code the check for err!=nil can declare:
func errcheck(err error) {
if err != nil {
log.Fatal(err)
}
}
then use:
errcheck(err)
then your code becomes:
resp, err := http.Get("http://example.com/")
errcheck(err)
defer dclose(resp.Body)
body, err := ioutil.ReadAll(resp.Body)
errcheck(err)
fmt.Printf("%s", string(body))
IMHO a little cleaner perhaps? But I'll wait for the go aficionado's to correct me and highlight drawbacks.
EDIT
Thanks! #RayfenWindspear
Do replace log.Fatal(err) with log.Println(err) to avoid unnecessary panic.
EDIT2
dclose() to avoid confusion with go close()
Have fun!
From what I could gather, net/http can't err. However, I would rather assume that .Close() can err for any implementation of io.Closer than to study the internals.
Below is an example using named returns where defer only sets the returned error if the error would otherwise be nil:
func printResponse(url string) (retErr error) {
resp, err := http.Get(url)
if err != nil {
return err
}
defer func() {
err := resp.Body.Close()
if err != nil && retErr == nil {
retErr = err
}
}()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
fmt.Printf("%s", body)
return nil
}

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