Handle Exceptions like a Professional (Part 2)
In Part 1, we looked at how runtime exceptions behave and why this behaviour makes them so useful in the right situations.
In this article, we’ll dive into how the throwing and catching/handling of runtime exceptions are two sides of the same coin. We’ll study a better way of organising our exceptions — The Exception Zoo.
Disclaimer: I love C#. Therefore most of my code examples are in C#. With minor language-specific modifications, most if not all of the shown C# code will also apply to other object-oriented languages like Java & C++.
When reviewing code, I still see plenty of instances where the developer has chosen to instantiate and throw the plain Exception base class, as shown below:
I wonder what the thinking here is. To the best of my knowledge, this approach offers no advantages.
What is wrong with throwing a base Exception?
- Must Catch All Exceptions. Since we are throwing an instance of the most basic exception type, the only way for us to catch and handle the exception is to catch all exceptions. It’s akin to putting a net out to catch all fish even though we may only be interested in Yellow Fin Tuna above a specific size.
- Identify Exception by Message. The only way for us to identify and distinguish this exception from others is via the Message property set during construction. Another developer could easily change the Message string of the thrown exception. The catch part of the code would be fragile — this is a coding antipattern.
Unless the application is trivial and there are only one or two instances where the code throws exceptions, I suggest you avoid this approach.
OK, so what is a better way?
First, let’s take a look at what we would like to do with exceptions when we catch and handle them.
Often we are in a web context. In a web server application, we respond to requests with an HTTP status code. When our application code on the server runs into a problem, the HTTP response status code can be one of two types:
- It’s the client’s fault — The client has sent through bad data. Error codes: 4xx (eg. 400 — Bad Request)
- It’s the server’s fault — The server has failed to respond to a valid request. Error codes: 5xx (eg. 500 — Internal Server Error)
Let’s look more closely at the 4xx client errors:
- 400 — Bad Request. The server should respond with this HTTP status code when the client is sending through invalid data, and a business rule in our server-side code is triggered.
- 401 — Unauthorised. The authentication details in the request are either lacking, incomplete or wrong. This status code has the wrong name — it should be 401 — Unauthenticated.
- 403 — Forbidden. The system is letting the client know that they are not authorised to perform this action.
- 404 — Not Found. The server could not find the given resource. It may not ever have existed, or someone may have deleted it already.
Authentication vs Authorisation — Quick Interlude
The concepts of Authentication and Authorisation are frequently mixed up. Let’s clear them up. Authentication means the identification of the acting party, a person, service or account. On the other hand, Authorisation is the assigning of rights (or lack thereof) to carry out a particular action on a system.
A few more pointers:
- Authentication must always come before Authorisation.
- Authorisation is always binary, either true or false.
- Authorisation applies to an action or resource.
Real-world example: Exclusive Nightclub
Let’s consider these terms in the situation where you’re trying to get into an exclusive nightclub. When the bouncer is asking you for your Id that is a request for Authentication. Say, you haven’t got your Id on you. The bouncer will decline your request for access immediately (i.e. Authorisation) since you cannot even identify yourself. If you do have your Id then you can at least pass the Authentication step — you can identify yourself. When the bouncer then checks whether you are on ‘The List’, that is your Authorisation — you’re allowed to go in (or not).
Let’s return to our HTTP 4xx status codes
To recap, HTTP 400 — Bad Request, 401 — Unauthorised, 403 — Forbidden and 404 — Not Found, are the most typical client-caused HTTP error codes on the web.
Would it not be useful if in our server web code we could catch precise exceptions that in turn convert to error responses with these error codes?
For example, these exceptions could convert to these HTTP status codes:
- ClientInputException ⇒ 400 — Bad Request
- UnauthenticatedException ⇒ 401 — Unauthorised
- UnauthorizedException ⇒ 403 — Forbidden
- NotFoundException ⇒ 404 — Not Found
Well, this would be quite useful, I think.
Hold on, if the exceptions cleanly convert to the error response codes then why not name the exceptions after the HTTP status codes? For example, 400BadRequestException or 403ForbiddenException?
The answer lies in the possibility that our code should be usable outside of a web context. Therefore it’s a great idea to keep the web out of the names of the exceptions. We are better off naming our exceptions along the lines of business logic concepts like ‘client-received invalid data’ (e.g. ClientInputException) which are independent of web.
I suggested that ClientInputException map to the HTTP status code 400 — Bad Request. Naturally, there will be many ways in which the client may deliver invalid data to the server. Hence there will exist many different ClientInputExceptions types. In a personal finance coding project, I have many ClientInputExceptions. Three examples:
Definition of ClientInputException in my project:
and for InvalidAccountGroupName:
Please note that IMHO the ‘Exception’ suffix is unnecessary on specific ClientInputExceptions. Why? It is easy to tell that it is an exception as the class derives from the base ClientInputException. We can also be sure it’s an exception because the code throws it. Only exceptions get thrown.
With this InvalidAccountGroupName exception, the original code throwing the first plain Exception base class becomes:
Isn’t that much tidier?
Catching of ClientInputExceptions
Never mind which exact ClientInputException the business logic throws, it is caught and re-emitted as 400 — BadRequests in the HandleException method:
The exercise of looking at specific exceptions that are the cause the 400 — Bad Request HTTP response status code can be extended to other 4xx HTTP status codes also.
A veritable zoo of exceptions can be created, grouped by different base classes:
This situation is not unlike a real zoo where animals species of different classes, like birds, mammals, fish, are all housed together.
Deriving exceptions from base classes that correspond to similar error conditions, like authentication failures or client input data errors, is recommended. Following this general rule allows for the wholesale treatment of exceptions of a kind. In the end, the system will contain a zoo of exceptions derived from a handful of exception base classes.