A Lesson in Error Handling in Go
Go, as a language, is cool. It’s fast, and it has some great features of modern languages. If you’re looking at this post, I’m sure you know that already.
One of those features, multiple return values (MRV), affords developers unfamiliar with MRV a new opportunity for performing error handling, and I’d like to demonstrate a small lesson that I learned while refactoring some code in order to make it more fault tolerant. The fact that I have not ever spent any time in a language that encourages the use of MRV really colored my thinking and created some unnecessary work/stress for me.
The old way
In languages like PHP, with a single return value from database functions (which I’ll use simply because they provide good example material), the developer has to explicitly check that things work.
The developer is attempting to connect to a database, and then checking for errors. Normal enough. This paradigm is pretty standard for most language paradigms. So, because I come from that paradigm (BASIC -> Pascal -> C -> PHP -> Ruby -> Obj-C -> Go), I expected to do things in a similar manner. This made my code look like this.
There is nothing wrong with this kind of code, but it (to paraphrase Spock) demonstrates single-return-value thinking. First, perform your action. Check for a problem, and handle it if needed. Continue executing if no error is found.
Multiple return values, however, let us turn this on its head. We can simply perform this entire structure inside nested if… statements.
Don’t get me wrong. You could design a pattern exactly like this using a language like PHP, but there is not any real benefit to doing so. Because PHP is runtime interpreted, and run each time the server requests the script, rather than compiled and resident in memory, a panic()
is not particularly bad. If you can’t connect to your database, then you may as well just error with a message saying something is wrong. But, with Go, you don’t want to. The application needs to continue running, so there is great value in checking for err == nil
prior to performing your next step, and because of MRV, there is no significant overhead to doing so, and no need to call another function to see if an error exists. Your application just keeps going as long as err
is nil
.
Your mileage may vary, but this works pretty well for me, and it was useful to understand why I wrote the first set of code.