In this post I’m going to talk about software errors/exceptions and how you can craft better software by giving them appropriate design considerations. I’ve often found that developers, even experienced developers, don’t put much thought into the error objects that they produce.
This article is designed to be language agnostic, just like Raygun itself. I use ‘error’ and ‘exception’ interchangeably and mean the same thing.
1. Be specific
When an exceptional situation occurs and you wish to throw an exception for that state, what type of error should you throw?
When I was a junior developer, I’d frequently find an existing exception type and just use that. A common example was ‘ArgumentOutOfRangeException‘ because that often was the error that had occurred – a method had received a parameter that was outside of the bounds for whatever I was doing. That’s totally fine for simple exceptions like a legitimate out of range exception.
Outside of the lower/simple level, say, in your business logic or domain model, I’m a big believer in writing your own exception types.
It is fairly straight forward to quickly create your own exception types in most languages. The base type for an exception is fairly deeply built into any language and usually you can inherit from that to implement your own types.
2. Great software error messages
With Raygun, we’ve literally seen billions of errors reported to us. We’ve seen the good, the bad and the ugly of software error messages.
A common reason for bad messages, is that we developers like to load the context into the error message. For example, we won’t typically choose to throw an exception with “User not found.” as it doesn’t help us debug in a hurry. We’ll throw “User not found: firstname.lastname@example.org, id: 2133”, giving us context on the error right there in the message.
While that seems helpful, it is not a wise idea.
Firstly, it leaks implementation details to any users who might see the error message.
Secondly, there’s only so much context you can squeeze into the message.
Thirdly, there is a better way…
3. Use error properties
Two options exist here. Remember my suggestion that you create your own exception types? This is where it shines.
You can simply add properties to your implementation that states properties that matter to you. Perhaps it’s attaching the user data, the SQL query that failed, or the primary key of a domain entity that was involved in the error. Easy.
The second option, which is a little more language specific, is data dictionaries. Take for example the .NET framework. The Exception base type has a ‘Data‘ dictionary property. You can load in any contextual information in that dictionary to be provided alongside your error – nice!
4. Only use exceptions in exceptional cases
This is more a general rule, but exceptions are supposed to be exceptional. They break control flow making it difficult to understand the repercussions of an exception. That means it can be hard for other developers to understand your code.
Further making this worse, exceptions are often treated in special ways by the host runtime. In managed languages like Java and .NET, an exception causes the entire runtime to do a lot of work — building stack traces, running a bunch of tasks to handle the error and more. This all leads to performance implications if you’re throwing too many exceptions.
Exceptions should be exceptional.
5. Do something with them
OK, so this one is a bit cheeky – but if you do have errors being thrown, you want know about it.
That’s exactly why we built Raygun in the first place: so you can see the health of your apps, and then fix things. Not a Raygun user yet? Well, luckily we have a free 14 day trial. Try it out for yourself!