This is a response to the draft design for Go 2 error handling.
I think that the main thing I dislike about the proposed check
/handle
blocks
is that they feel too much like a kind of "magic goto" that breaks the normal program flow.
I worry that it won't be straightforward to follow the logic of what happens when
a check
fails. In the simple handle err { return err; }
it isn't so bad but
the examples with chained handle
blocks seem quite counterintuitive.
I'm in agreement with Alessandro Arzilli
in that I think chaining is the main source of my objection. It seems to me that the
burden of proof ought to be to show that the chaining mechanism is necessary. Certainly
single-handle
case is the overwhelmingly most common case; how common is the
more complicated one that uses multiple handlers? Examining existing code should
make it possible to determine whether the extra complication is really necessary
(a simple metric might be: how many if err != nil
blocks contain statements other
than a return
?)
Alessandro proposes an alternative design where one handler is allowed per block, I'd go further and suggest it should be a limit of one handler per function. My rationale is:
- It clearly satisfies the common case of "just want to pass back an error"
- It aids readability since it will always be trivial to see the handler that will be
invoked in response to a failed
check
. - Without chaining,
handle
blocks must (?) always end in areturn
, which links them to the function scope rather than the block scope.
Note that single-handler is still enough to support the cases that require chaining;
they simply require splitting code across multiple functions.
For example the CopyFile
example looks like this:
func CopyFile(src, dst string) error {
handle err {
return fmt.Errorf("copy %s %s: %v", src, dst, err)
}
r := check os.Open(src)
defer r.Close()
return check performCopy(r, check os.Create(dst), dst)
}
func performCopy(r, w *os.File, dst string) error {
handle err {
w.Close()
os.Remove(dst)
return err
}
check io.Copy(w, r)
check w.Close()
return nil
}
The main ugliness seen here is the need to pass the dst
parameter to the second
function, but it isn't too bad.
I agree that having chain of handlers is too confusing but then having a single handler per function makes it very much like defer.