This is my own iteration that build upon PeterRk's proposal. https://gist.github.com/PeterRK/4f59579c1162cdbc28086f6b5f7b4fa2
The idea is to reduce if err != nil {}
boilerplate while not encouraging developers to write lazy error handling.
For me, this means:
- Add as little syntax as possible
- Be as explicit as convenient
The user PeterRk proposed the next syntax:
func getDivisorFromDB(key string) (uint, error) {
//...
}
func GetDivisor(key string) (uint, error) {
exit := func(err error) (uint, error) {
return 1, fmt.Errorf("fail to get divisor with key \"%s\": %v", key, err)
}
divisor := check(getDivisorFromDB(key), exit)
//...
return divisor, nil
}
with
divisor := check(getDivisorFromDB(key), exit)
being equal to
divisor, err := getDivisorFromDB(key)
if err != nil {
return exit(err) //return err
}
I think it's the right direction, and we can improve it.
- Not explicit return statement
- Sometimes the abstraction is unnecessary and make the code harder to read
So with a simple change in the syntax, we get:
divisor, err := getDivisorFromDB(key) or return exit(err)
The or
keyword detects if the last returned value (which must be an error) is different from nil, in which case executes the function in the right.
We can omit return
and the code will continue to execute. If we do so but exit
return something it will be discarded just like in regular Go code, this way the function is more reusable.
Note that exit()
is a regular call, so is very flexible with parameters/arguments.
And there is no need to consume err
, actually thanks to that is orthogonal with the naked return, so we can do:
func GetDivisor(key string) (divisor uint, err error) {
divisor, err = getDivisorFromDB(key) or return
return
}
I am not too sure if this is possible to implement or there are any weird corner case I am not contemplating, this section is just for debate sake, but we can take this opportunity to add error checking to defer. If we can make that or
get triggered even if we don't save the returned error in a variable
defer f.Close() or return errHdl("", fmt.Errorf("couldn't close file"))
Or we can go even further and now think that it makes sense to save the variable (?)
defer err := f.Close() or return errHdl("couldn't close file", err)
func Foo(path string) ([]byte, error) {
errHdlr := func(reason string, err error) ([]byte, error) {
return nil, fmt.Errorf("foo %s %w", reason, err)
}
f, err := os.Open(path) or return errHdlr("couldn't open file", err)
defer f.Close() or return errHdl("", fmt.Errorf("couldn't close file"))
result, err := io.ReadAll(f) or return errHdlr("couldn't read from file " + path, err)
return result, nil
}
vs
func Foo(path string) ([]byte, error) {
f, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("foo %s %w", "couldn't open file", err)
}
result, err := io.ReadAll(f)
if err != nil {
return nil, fmt.Errorf("foo %s %w", "couldn't read from file " + path, err)
}
err = f.Close()
if err != nil {
return nil, fmt.Errorf("foo %s %w", "couldn't close the file " + path, err)
}
return result, nil
}
This is a very simple example, but we can already see the benefits. The programmer that is reading the code can even focus on the left side and ignore error handling.
What do you think about gofmt aligning or
s?
f, err := os.Open(path) or return errHdlr("couldn't open file", err)
defer f.Close() or return errHdl("", fmt.Errorf("couldn't close file"))
result, err := io.ReadAll(f) or return errHdlr("couldn't read from file " + path, err)
The problem comes when a user abuse of this system when good old if != nil {}
is enough.
We can guess that lazy programmers (as myself) or those who are in a hurry or just still learning (it's me again :D) prefer to write an if
more than a regular or anonymous function.
Or we can make the opposite approach and allow a single return line next to or
, but I don't like that that much, it would bring too many inconsistency because there would be situation where if != nil {}
would still be the more appropriate, in other words just too many ways of doing the same things with subtle differences, we obviously want to avoid that.
Thanks to this code I was cured of my low blood pressure.