Correct Error Handling is Hard
Just recently, I was reminded how hard correct error handling is, while using the Go client for Google Cloud Platform.
The code I was writing looked like this:
Everything seemed fine, except
my-object was not created!
Inspecting the whole thing a bit closer, it turned out that
writer.Close() (line 2) returned an error saying that the client is not authorized.
My first reaction was, that
io.Copy(writer, src) should return that error already. But giving it some more thought, I realized that this isn’t possible, and furthermore, it’s perfectly fine for an I/O object to return an error only when closing it.
The point is, an I/O object can return an error either (1) while acquiring it, (2) while reading from/writing to it, or (3) while closing it. This is part of the contract.
So the often used
defer ioObject.Close() relies on the assumption that errors returned from
Close() can be ignored. This isn’t true in general. Examples being the writer from the example above, or even a simple
Not checking the return value of close() is a common but nevertheless serious programming error. It is quite possible that errors on a previous write(2) operation are first reported at the final close(). Not checking the return value when closing the file may lead to silent loss of data.
Unfortunately, The Go blog — Defer, Panic, and Recover recommends exactly that:
As you can see, none of the
Close() errors are handled.
Correct Error Handling
What would a version of this look like with correct error handling? Like this:
What changed? We removed the
defer calls and replaced them with explicit
Close calls in all code paths. Now, when an error happens during
Copy (line 13) we return an error, but close the two files before (lines 15–17). We ignore the error from
Close because this either succeeds or it fails, but at that point it might be just a consequence of the previous error that has already happened. After the copying is done, we close the files, but explicitly handle the errors from those calls and do not ignore them.
Is this verbose? Yes, it is! When you handle errors correctly, code becomes verbose. It’s also your lifeline when something in production really goes wrong.
Unfortunately, this code has some issues as well:
- it contains a lot of duplicate lines
- it’s difficult to get the
Closecall logic right
- it won’t close files when a panic happens somewhere
Can we do better?
The SafeCloser Approach
It turns out a little helper, that I call
SafeCloser, can help:
It’s really just a safe-guard so you want close a
Closer multiple times. A lot of
Closer implementations are already idempotent, but there is no guarantee. The
SafeCloser is that guarantee.
How does it work in action? For every
Closer you simply create a companion
SafeCloser and close your
Closer through this
Example for our
Note how I declared a
SafeCloser before every
defer block and used in there (lines 6, 7, 13, 14). That same
SafeCloser is then used at the bottom (lines 21 and 26) when we explicitly close the files.
We avoided the duplicate lines and panics are handled correctly.
I deliberately did not implement the
io.Closer interface with the
SafeCloser to avoid misuse. It is not intended to wrap any
io.Closer, pass it around, and be lax about closing them several times. Instead, it’s for this very specific use case, where you handle
Close calls differently depending on if you are in failure mode or in success mode.
Correct error handling is hard. Most often it is not done correctly. A little helper like the
SafeCloser from above can help make your error handling correct, while keeping it as simple as possible.
If you liked this post, you may also be interested in my follow-up post, “Correct Error Handling is Hard, Part 2".
Hacker Noon is how hackers start their afternoons. We’re a part of the @AMIfamily. We are now accepting submissions and happy to discuss advertising & sponsorship opportunities.
To learn more, read our about page, like/message us on Facebook, or simply, tweet/DM @HackerNoon.
If you enjoyed this story, we recommend reading our latest tech stories and trending tech stories. Until next time, don’t take the realities of the world for granted!