I’ve been recently drawn twice into an open discussion on when to throw exception in application’s logic.
I’ve had my opinions, others have theirs but what lead me to this post is that such fundamental topic is extremely opinionated, even among same language/environment developers.
In next few paragraphs I’ll try to sum up major proposed approaches of using exceptions (both those which abuse them and those which antagonize them).
Examples will assume that you are not using models as property bags but you actually have logic in them (vide DDD). Having that in mind, we’ll skip the fact that most probably, user receives ‘first-line’ validation of inputs on the UI.
To simplify, let’s impersonate two popular approaches with two fictional developers: 💂 Cpt. Exception (throw exception carelessly) and 👑 King Status (rarely throw exception or not even at all; loves messages, statuses and enums as responses).
Let’s play around with few cases and think what might be right or wrong with each approach.
Use case: File access (and other I/O)
This is a common case when Cpt. Exception and King Status rarely disagree.
Whenever there is a problem on attempt to access file, that we do not have prior knowledge of (or its cheaper to throw), we just throw exception:
- We mostly do not have further info with which we could work with – in most languages and libraries its rather a system exception/error that we can just handle for user and that’s all – exception as a way to go
We could’ve of course somehow translated exceptions to some status codes or responses but there is no reason of doing so in such case.
Use case: Change object property with restrictions
Let’s take a look on simple business case. We have a Product domain model (in DDD-like sense) that has a price. Price can be changed be user that is managing shop, but let’s put permissions for actions aside as they belong to application layer logic, rather than business logic.
Price of a product cannot be changed below certain level. Let’s assume it’s a level of profitability but it can be any other value that makes sense for business.
It cannot also change if product is no longer sold. It might be a policy of a company (archived products and so on) or just a simple requirement that it doesn’t make sense for users.
What would Cpt. Exception do? Probably throw exception on each case:
- We throw exception when core business rule (price > level of profitability) is broken
- We enforce change caller (probably application root) to react to an exception; there at least need to be some try-catch clause
- Exceptions are generally costly as opposed to some internal messages
- We also throw on non-core business rule (cannot change if is no longer sold)
- Commonly known pattern, know as try-catch-do_nothing can lead to exception being meaningless
What would King Status do? Introduce some sort of return enum or response to notify user with:
- No overhead when compared to throwing an exception
- Easier to deal with if you want to work with status of change somewhere else, not only deal with invalid change
- If combined into some sort of response, can also store some sort of message what happened, similar to exception etc.
- Operation must always return some sort of status or response, even if everything went fine
- Can be much more easily ommited (on purpose or by mistake) because most of languages do not enforce usage of values returned by methods, as opposed to try-catch when you throw exception
- (minor issue) Makes validation break some languages features – for example we cannot return from C# property, therefore we need to change to plain old methods
I would generally mix up two approaches and throw on broken core business rule only while giving some sort of status (or even boolean return value) for non-core validations.
I like exception approach but I can see status/response pluses too.
Use case: Return entity of given identity
Let’s imagine two queries on our persistence layer, that we want to provide us with: category of given id and products from category of given id.
Cpt. Exception would do something like this I suppose:
Plain, good, old exceptions eh?
I can see at least one place when we could remove exception with no logic change at all.
Let’s take a look on status/response King Status came up with before pointing that out:
Uh, those statuses and responses all around again?
Feels quite ugly eventually, doesn’t it?
Also we need to keep in track with all those statuses that will come up, for all the flows of operation out there (or at least errors + default).
But! We can once again mix things up and try to leverage common sense with something like this:
We could also refine it further and return null (or empty array/collection) on second case if what we actually meant was “find products that have category of such id” instead of “find me category of that id and all of its products”.
A little difference but as always, it depends!
Drawing up conclusions
As I said, recently I’ve had some discussion on that topic, but I’ve made it to one more place to confront people opinions.
Together with other developers in Polish community, we came up with few ‘rules of a thumb’ that will make it easier to decide – to throw exception or not.
First things first
If you are joining ongoing project, find out what coding standards have been introduced and organizational decisions have been made.
Maybe you are not about to make a decision as it has been made already for you?
You can of course confront current state of things if you think it’s wrong and some thoughts below will help you out with that.
Keep in mind, that’s still my opinion, but maybe it will help you out making up your own.
When to throw exception:
- If you cannot really do a thing with what happened – external services or I/O failure
- If you have any missing operation preconditions (empty method parameters, empty ctor parameters etc.)
- If you feel more like enforcing other developers working with your API to react on invalid operation/data (can be skipped by empty catch though)
- If you do not expose exceptions to outside ‘world’, ie. your application root always handles exceptions before communicating them to outside
When not to throw exception:
- If you’re about to throw a generic, base system exception, you’re actually better off not throwing them at all; only meaningful and understandable exceptions make sense. No one will parse your exceptions messages to find out what you meant before reporting to user.
- If certain exception is often hit in certain place (no longer meaningful)
- If operation is in application root and only possibility is that it will be thrown either to end user (web/desktop/mobile app) or to transport layer (web service endpoint)
- If answering questions like “IsSomething”, “FindByPredicate” (no match); generally do not use exceptions as your business flow logic and decision branching tool
- If you meant to return multiple different yet valid outcomes of certain operation (seriously, use responses or any other meaningful object instead)
Precautions on throwing exceptions:
- Throwing an exception is costly. If performance is critical, try to avoid them (and move to lower level language perhaps?)
- If something happens frequently, its not an exception (I’m duplicating this because it seems to be not so obvious to some)
- Exceptions are not a way to communicate with user! They are meant to point places where application could perform invalid operation, based on conditions you do not expect, that later on could also lead to data with invalid state flying around. You are notified that you are meant to react on such occurence, that’s what they’re for.
“…throw exceptions at other developers, never at users.”
Unknown Twitter poster, 2016 (let me know if you know real author)