The only two cases when you should handle exceptions
Exceptions are often handled indiscriminately, but there are only two cases when you should do it. Here’s how to make the best use of exceptions.
5 min read
It is very common to find applications that use exceptions to report data validation errors directly to the user, or to control application flow.
But exceptions are a technical device, made for programmers and technical people, and should not be used to report errors to the user about things the user can do to correct them.
Exceptions represent a bug in the software, a mistake made by the programmers, or an unexpected situation such as network outages.
So, there are two very specific guidelines on when (or where) to handle exceptions.
1. At the lowest possible level
This is the level at which you are integrating with third party code, such as an ORM tool or any library performing IO operations (accessing resources over HTTP, reading a file, saving to the database, you name it). That is, the level at which you leave your application’s native code to interact with other components.
At this level, unexpected problems out of your control such as connection failures and locked files may occur.
You may want to handle a database connection failure by catching a
TimeoutException so that you can retry after a few seconds. The same goes for an exception when accessing a file, which may be locked by a process at the moment but be available at the next instant.
The guidelines in this scenario are:
- Handle only specific exceptions, such as
IOException. Never handle a generic exception (of type
- Handle it only if you have something meaningful to do about it, such as retries, triggering compensatory actions, or adding more data to the exception (e.g. context variables), and then re-throw it
- Do not perform logging here
- Let all other exceptions bubble up as they will be handled by the second case
2. At the highest possible level
This would be the last place where you can handle the exception before it is thrown directly to the user.
Your goal here is to log the error and forward the details to the programmers so they can identify and correct the error. Add as much information as possible, record it, and then show an apology message to the user, as there’s probably little or nothing they can do about it, especially if it is a bug in the software.
The guidelines in this second scenario are:
- Handle the generic
- Add more information from current execution context
- Log the error and notify the programmers
- Show an apology to the user
- Work it out as soon as you can
1. Exceptions represent irreversible errors
Again, the reason behind these guidelines is that an exception should be used to represent a bug in the system, a mistake made by the programmers, or a situation beyond the control of the application.
In these cases, there is usually nothing the user can do. Thus, the only thing you can do is log the error, take the necessary compensatory actions, and apologize to the user. If it is a mistake that the programmers made, it is best to let them know and fix it, working towards a more stable version.
You’re likely to have many errors early in the life of an application, but you can also fix them early on, generating a stable, almost error-free application.
A validation error does not fit this scenario because it usually has to do with bad data entered by the users themselves. Data validation should have been done at the presentation layer, where you could return a friendly message right away, before submitting them to the inner parts of the system. If invalid data has reached the heart of the application, it classifies as a programmer mistake, as he or she failed to handle the data at the correct layer. In this case, it is best to let an exception blow up, as it will be logged at the highest level, and to apologize to the user.
try catch blocks mask application execution flow
try catch block has a similar function to that of a
label and its
goto companion, which causes the application execution flow to jump from one point to another.
Indiscriminate use of try catch blocks will make you play hide and seek with your new bugs 🙂
See posts like this to understand why goto is evil.
If you use
try catch blocks indiscriminately throughout your application, you may be creating situations and errors that will be difficult to find and debug later.
try catch blocks only for exception handling according to the guidelines above.
Bonus: when to throw exceptions
Easier to explain in the context of developing a library. You should throw when you reached an error and there’s nothing more you can do besides letting the consumer of your APIs know about it, and letting them decide.
Imagine you’re the developer of the ADO.Net library (a data access library). When you reach a network error here, there’s nothing you can do besides throwing an exception. That’s an irreversible error from a data access library standpoint.
This is different when you’re developing a website. You would likely catch such exception in order to retry the operation, but would want to throw an exception in case you received invalid parameters from outer layers since they should have been validated there.
Which is again different in the Presentation layer, where you expect the user to provide invalid parameters. In that case you just show a friendly message instead of throwing an exception.
- Catch specific exceptions at the lowest possible level when you can do something relevant about it. Otherwise, let it bubble up
- Catch generic exceptions at the highest possible level, so that you can log them and apologize to the user
- Throw exceptions when you reached an irreversible error situation, according to the context you’re in