"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
}
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
}
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)
}
}
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)
}
}
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
}
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)
So, to sum up:
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.
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.