When I was at university learning Java, Exceptions seemed ubiquitous, it was simply the way to do error handling. It did lead to code with many throws
in function declarations and try/catch
blocks sprinkled all over. It felt a bit awkward, but I didnโt question it too much.
Now many years later, I look at the concept of exceptions completely differently. I guess this is a general trend, given that Kotlin (what I consider to be modern Java) doesnโt even support checked exceptions.
My opinion on exceptions is this:
Exceptions should be exceptional
What a shocker.
The appropriate times to throw exceptions
Since exceptions should be exceptional, I only see 2 valid cases to throw exceptions:
- Inherently unreliable code (network/disk/โฆ)
- Unrecoverable cases
You could even argue against inherently unreliable code, but Java code is often implemented that way, so you will run into exceptions in those cases.
How to handle exceptions
There should also be some guidelines on how to handle the exceptional exceptions being thrown.
Exceptions that are thrown by inherently unreliable code
Exceptions from unreliable code should be handled as close to the source of the exception as possible. All other code should handle errors through its return type, for example, nullable types or an even more explicit concept, such as a dedicated class. The return type takes the role of what used to be the throws Exception
keyword in function declarations.
Using return types for error handling will force you to do more checks on failure cases, but that doesnโt necessarily bloat the code if languages have explicit language support for [safe calls as Kotlin does][4].
Also, using return types makes all potential code paths much more explicit, making it easier to reason about a large code base.
Exceptions that are thrown by unrecoverable cases
Unrecoverable cases are unrecoverable. In this case, the exception signals that the entire request should fail or the entire program should crash. There can be a single try/catch
block at the bottom of the stack to map the exception to an error state but thatโs it.
An example would be throwing a ForbiddenException
when a user doesnโt have appropriate permissions in an HTTP request, which can be transformed by the mapper to an HTTP 403 Forbidden.
Now of course, nobody is stopping a developer from catching that exception and executing an alternative code path, but that means exceptions are being used for control flow, which is a big no-no and should never pass code review.
Conclusion
There are of course always exceptions (no pun intended), but when following these rules, there shouldnโt be any exception-handling in your business logic. This makes the code explicit and easy to reason about. In the rare cases where exceptions are needed anyway (although I canโt think of any), making sure the behaviour is well-documented is the way to go.