Getting error handling right

Yan Babitski
Dec 15, 2019 · 6 min read

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
  • 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:

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:

Questions to answer when dealing with error

What if our guideline is the following: “always use standard errors?” That’s two fewer questions to think about (3 for Java). And not only that.

With custom errors, there would be additional problems:

  • Discover already present exception/error status. Would custom exception classes be located in utils? Or exceptions? Or maybe errors? Is it in our project or in some sort of common shared library? Which common library: <project-name>-common, just common, or maybe we have common-errors? I’m sure you had this situation.
  • Deal with similar or even completely equivalent statuses or exceptions. In this module we use mapper.errors.NotFoundError , but this library returns geo.statuses.NotFoundErrorStatus. So we should probably check and convert the latter to the former.

These problems are eliminated entirely if a set of standard error statuses/exceptions is used. E.g. use one from Google. Is the file absent? Return NOT_FOUND. Invalid data was provided — INVALID_ARGUMENT. Request to withdraw submitted before money arrived — FAILED_PRECONDITION. Sometimes the choice might be tricky, but these 15 codes should work for 90% of the cases.

For the remaining 10% of cases, we would have to create custom error codes/exceptions.

Idea #4. Treat custom error same as a custom class loader

It’s very easy to create an exception. Create a new class, extended it from Exception or std::exception and done. Does it mean custom exceptions should be all over the place? It doesn’t.

Exceptions or error codes should be treated similarly to custom class loaders or memory allocators. Or custom collection type. You won’t create CustomerArrayList so don’t create CustomerNotFoundException. Errors are part of the infrastructure, not business logic.

All these measures are supposed to help with accidental complexity, help to focus on important things, and not on solving riddles of implementation details.

Though probably not all of these ideas will work for you as is — no silver bullet. There is no single technical answer to get error handling right.

Getting error handling right is partially about technical details: what works best for your project and codebase, but a big part of it is about communication and leadership: how to get other engineers on board, come up with good enough approach, and do it in a reasonable amount of time. The ideas here are not ideal, but it could be a good starting point to work out a different set of guidelines that would fit your team and organization.

The Startup

Medium's largest active publication, followed by +567K people. Follow to join our community.

Yan Babitski

Written by

Software engineer @ Google

The Startup

Medium's largest active publication, followed by +567K people. Follow to join our community.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade