DEV Community

Cover image for [Go] unusual: move "if err !=nil" into struct methods
Julian-Chu
Julian-Chu

Posted on

[Go] unusual: move "if err !=nil" into struct methods

#go

"if err != nil" is a pain in golang error handling, you may need to write the following code many times.

if err != nil{
     return err
}
Enter fullscreen mode Exit fullscreen mode

Common case

For example, if we'd to execute some actions, the error check comes after each function.

func DoActions() error {
    err := Action1()
    if err != nil {
        return err
    }
    err = Action2()
    if err != nil {
        return err
    }
    err = Action3()
    if err != nil {
        return nil
    }
    return nil
}
Enter fullscreen mode Exit fullscreen mode

With error handling function"

The idea to handle error explicitly is good, but it also makes the code longer and noises in readability. One way very usually used to reduce lines of code and keep readability is to have a error handle function in some cases.

func DoActions()  {
    err := Action1()
    handleError(err)
    err = Action2()
    handleError(err)
    err = Action3()
    handleError(err)
}

func handleError(err error) {
    if err != nil {
        log.Fatal(err)
    }
}

Enter fullscreen mode Exit fullscreen mode

Error check in bufio.Scanner

Here is another way to reduce code of error check by method. The idea is to use error field inside struct for methods. One example you can find in the bufio.Scanner

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    scanner := bufio.NewScanner(os.Stdin)
    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }
    if err := scanner.Err(); err != nil {
        fmt.Fprintln(os.Stderr, "reading standard input:", err)
    }
}
Enter fullscreen mode Exit fullscreen mode

The Scan method doesn't return any error, the internal error of struct is checked after for loop. If going to the Scan() method, we can see the Scan() only set internal error and use internal error as flag.

Rewrite first example with struct error field

let's take the struct error field idea to refactor first example.

func DoActions() error {
    ag:=&ActionsGroup{}
    ag.Action1()
    ag.Action2()
    ag.Action3()
    if ag.Err() != nil{
        return ag.Err()
    }
    return nil
}

type ActionsGroup struct {
    err error
}

func (ag ActionsGroup) Err() error {
    return ag.err
}

func (ag ActionsGroup) Action1() {
    if ag.Err != nil {
        return
    }
    // if something wrong, set ag.err
}

func (ag ActionsGroup) Action2() {
    if ag.Err != nil {
        return
    }
    // if something wrong, set ag.err
}

func (ag ActionsGroup) Action3() {
    if ag.Err() != nil {
        return
    }
    // if something wrong, set ag.err
}
Enter fullscreen mode Exit fullscreen mode

Obviously it doesn't reduce the lines of error check, instead, just encapsulate error check into each method, and make code block for high level business logic clearer and less code for error handling.
No silver bullet. This can't be applied to all cases, but if you need error check between methods of one struct or can group functions with high cohesion into struct, it could be an alternative way for you.

Top comments (3)

Collapse
 
andreidascalu profile image
Andrei Dascalu

So, to sum up:

  • it doesn't reduce the err!=nil checks
  • you say it makes code clearer but I disagree since it hides the error handling AND you will be doing a number of pointless checks (you have an error, deal with it or return and stop the flow - but in this case your code keeps checking the error pointlessly) which isn't apparent by looking at it
  • you are binding together not just data with functions but functions between themselves. I can't think of a situation where that's desirable (though I guess it can shorten the parameter count in some situation at the cost of always having at least one pointer in play)
Collapse
 
julianchu profile image
Julian-Chu

Hi Andrei,
thanks for comments. Yes, l also write in the end, this way doesn't reduce lines of err check , only move the err check to methods. maybe I should find a better title..

For second, you're right it have pointless checks. IMHO I think it a tradeoff, the pointless check almost costs nothing and no performance impact, if it can reduce the err check code in the higher level code block, maybe worth.

For binding functions, I think I won't do this way for functions as well, only by methods in one struct which may share states.

Collapse
 
andreidascalu profile image
Andrei Dascalu

Well, that's not quite so. If it were only the if checks, it wouldn't be much overhead if you don't overdo it, but we're also talking about calling functions unnecessarily. When you call a function, there are all sorts of allocations happening and garbage collection. There's an overhead to function calls beyond whether they run their full code or not. You should run a profiler test.

There's another bit. When checking on demand, you may have a reusable err variable that only ever holds nil in happy case an at most one error which is collected at the end of function call.

I'm you case you have a full error allocated regardless of usage. Even in the happy case you have an error that's only garbage collected when the whole program ends.

Of course, the last point is also a nitpick in that it's not idiomatic.