Error handling: Is there a right way?

Nemanja Kukulicic
4 min readJul 21, 2024

--

Photo by Chris Ried on Unsplash

When developing software, coding out those features tends to be simple: make the business happy, make sure you won’t have a hard time when changing everything, and that someone new to the project does not need a month to get a grasp of a module. Of course that is far from simple, it is hard, but if you do it well, the result will be simple. I wanted to share some thoughts on one part of this whole N-headed beast, error handling.

What is an error? Is it when something goes wrong? Does it matter why something went wrong? I noticed that there are a few types of errors, so I subjectively categorized them into: Unexpected errors, user errors, and non-breaking errors. The names are self-explanatory, but the ways we handle these will be very different.

Since I am a backend engineer I will talk in the context of web APIs, as I am not naive enough to think that I can cover the vast field of software engineering. In my little bubble errors are generally handled using exceptions, result pattern, try pattern, or whichever magic your language provides. Let’s talk about the two most common ones that appear across all languages: exception and result pattern classes.

Exceptions

Exceptions are ugly and beautiful, said to be expensive but so easy to throw and eventually catch in something like a middleware where you handle all your exceptions. If you do something like this, you will eventually create an exception handling switch statement that does not fit on a vertical monitor. Especially if you handle each exception separately, though that is only the case if your exceptions don’t carry much so only their type is the information they provide. So let’s solve that problem, add some message and HTTP status code, if we enforce this throughout our custom exceptions we can have some commonality and make our global exception handler fit on a screen.

What about logging? First, we have to answer the question: what do we log? You will probably want to log where, what, and under which circumstances something happened. This will usually involve a path to your code, some human-readable message, and maybe some variables that can give us insight into the issue. So your first thought might be, why not just put that into the exception message? You could, but if you want your API to respond on-error with some message about what happened, you will end up with too much exposed information. Here we notice a line between a message you want to use in the response and the logging message. So why don’t we just add two messages to our exceptions, though it might not be that pretty to look at.

What about those non-breaking errors, where we only want to log something but keep the code running. Well, you won’t be using exceptions for that, but you could simply call your logger directly which is completely fine.

If you want to use exceptions to handle errors make sure you don’t end up with a codebase full of try/catch blocks. Also, be ready to defend yourself when someone tells you that exceptions are expensive and should be avoided.

Result pattern

Time to get away from those expensive exceptions, let’s just return result classes. Our result class can support our two types of messages, HTTP status code and whatever we might need. Using this pattern we take a lot of control over the control flow, which comes with more work. This work tends to be tedious and makes you carry, pack, and unpack this result object throughout your layers. If you think you need the performance you might gain by doing this, go for it, but make sure you and your team members have the discipline to keep using this pattern.

But don’t forget, you will probably need to handle exceptions caused by your framework/libraries. You could wrap each one of these with a try/catch block and convert that to a result object, but that would generate a lot of code and make for an awful development experience. So you will need a global exception handler anyway to pick these up.

The Response

In the end, whichever path you choose will lead to a response. Set your status code and the response body. After a few years, you will run into some third-party API that returns 200 status all the time even if something went wrong. This issue can be overcome if you look at the response body and check if it is what you wanted or an error response, but of course, it will be worse, that error response won’t be consistent. So let’s learn from these traumatic experiences, use those response codes consistently, define an error response body contract, and make the error messages understandable by someone who didn’t write them.

What’s the right way?

Of course, there are many right ways to handle errors. Throughout my work I tried many different ways, seeking perfection. I even created a .NET package that can help with error handling making it fluent and consistent. I hope I inspired you to think and come up with your way of handling errors. As an engineer, review your projects and find ways to improve error handling using techniques tailored to your project’s specific requirements. Don’t be afraid to be creative and try new ways to better your code.

If you’re interested you can check out my .NET error handling package

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

No responses yet

Write a response