Software engineers write programs that work. Great software engineers write programs that work in almost any condition.

You just spent 5 minutes typing in the name, addresses, and lots of other things in an online government form and got the “Server connection closed” message after clicking on “next.” You check in to a flight using a self-check-in terminal, and after finally solving the quest of using its interface, you got “Sorry, an error happened.” You are making a money transfer using a mobile app and step into an elevator where there are no signal and app freezes.

Not good enough error handling in software could cause a mild annoyance, frustration, and could even create problems that would take hours to resolve. And not only that. Bad error handling makes the product look unfinished, raw, sloppy. It undermines users’ trust.

So it’s good to have some thought put into “what could go wrong” scenarios and have them handled. It’s good to have reliable software that just works and doesn’t require the whole team of support engineers to put up fires over and over again. And part of this effort is to keep the code clean and have a consistent approach to error handling.

We’ve already discussed the difficulties with using the most common vehicle to deal with errors — exceptions, and also looked at what good does it give. Based on the observations, let’s try to come up with a few ideas on how to make our software reliable.

Idea #0. Guidelines (aka code style)

There is a lot of complexity in software engineering: even a simple webpage that displays some information from a database involves technology developed by thousands of people over the course of a few decades. And the complexity could be essential — because our problem is complex, or accidental — because the solution is convoluted. In other words, essential complexity is one that allows solving the problem, and accidental is one that just makes your life harder.

A natural way to get some of the accidental complexity is not to have code style. Then different projects across the organization or even different parts of the same projects would have different styles: variables named differently, different indentations, etc… It’s bad for two reasons:

Each time you are looking at code, you have to spend a little bit of time figuring out what is what. If code style is uniform and you see MAX_DELAY in Java or kMaxDelay in C++ you know it’s some constant

in Java or in C++ you know it’s some constant Each time you encounter some rule, you personally don’t like you might spend some time “fixing” it.

That’s why pretty much every company develops or uses style guides. The main value from it is not that it chooses “the best” style but that it chooses it. Uber’s “standard code style” puts it nicely:

The the whole point of standard is to avoid bikeshedding about style. There are lots of debates online about tabs vs. spaces, etc. that will never be resolved. These debates just distract from getting stuff done.

So it could be useful to develop guidelines for handling errors and stick to it. Though guidelines themselves are just the beginning.

It should be easy to do the right thing: libraries for relevant languages with all utility classes should be provided, as well as dev tools to take care of trivial things like indentations and variable naming.

And it should be difficult to do the wrong thing: there should be both technical (presubmit checks that won’t allow submission if there are style errors) and organizational (code review) mechanisms to enforce code style.

We could use the same approach for error handling. Have a few guidelines when dealing with error scenarios, so error handling becomes a matter of following these rules.

Idea #1. Use both exceptions and statuses

Sooner or later, there would be a case where we want to return either something or an error, which isn’t something exceptional.

The simplest example is to pass JSON provided by the user. Honestly, we don’t treat “user-provided JSON is invalid” as something exceptional — it’s a normal flow of things. One could argue that we could have two methods instead: “validate” and “process.” That’s a fair point, and it’s probably correct from an idealistic point of view. But in practice that would entail requiring the user to call two methods instead of one and making a data class for that JSON — ok things to do, but extra work. And we would still have cases where we want to return either value or error.

Some languages have native support for this pattern (e.g. Scala’s Try or Go’s multiple return values), for some language a custom should be created (though it’s shouldn’t bee too difficult): e.g. Google’s StatusOr .

Idea #2. Use exceptions for exceptional cases only

If we’re using both of the tools, we need a way to distinguish cases where to use statuses and where to use exceptions. You can start with the rule “throw an exception if and only if nothing could be done and current work should be aborted entirely” and see if it works for you.

Idea #3. Have a set of standard errors and use it everywhere

In the previous article, we discussed that an engineer should answer a few questions to handle the error: