Not All Exceptions Are The Same

Victor Todoran
5 min readFeb 7, 2023

--

Photo by Jose Francisco Morales on Unsplash

There is lots to talk about Exceptions: what are they, when should you throw them, how should you create them and so on and so forth. You won’t find any info about these topics in this post, we’ll leave that for another day.

Instead, you are going to find in this post a few opinions about which Exceptions you should document and which Exceptions you should catch in your code.

All the information in this Post refers to private code. That is business logic code which your application uses and is not shipped as a public library.

Documenting Exceptions

There is this hideous habit of documenting ALL Exceptions that might be thrown by a method in ALL places a method might exist.

If you’ve been around the block long enough you might’ve seen something like this in the docblock of a method.

   /**
* @throws AccountException
* @throws VeryGenericAndExtendedByManyOthersException
* @throws ErrorException
* @throws SomeEntityNotFoundException
* @throws SomeEntityNotSavedException
* @throws Exception_Invalid_Entity
* @throws Exception_Invalid_Another_Entity
* @throws Exception_No_Active_YetAnotherEntity
* @throws Exception_YouGueesedIt_TotallyDifferentEntity_Already_Saved
* @throws HookException
* @throws FatException
* @throws PrivacyException
* @throws ReflectionException
* @throws LockExcception
* @throws UnknownEventException
* @throws LowLevelException
* @throws LowerLevelException
* @throws LowestLevelException
* @throws BellowLowestLevelException
*/

Let’s start with when/where you should document Exceptions.

We document Exceptions which are thrown by a method to give clients of that method the possibility to catch them. We inform them: ‘Hey! This might happen’. It’s part of the public contract of the method.

If we are at the very edge of the Application, in a Controller or worst, a Test, who the heck are we informing? And why? It’s not like PHPUnit or the FrontController (both of which, in most cases are out of our control) cares about all this information.

Documenting Exceptions when there is no one to catch them is futile.

Your just making clutter, please stop.

Now let us talk about which Exceptions you should document.

Clearly you should document only the Exceptions that are meant to be caught, and some Exceptions are simply not meant to be caught by anyone else than the ErrorHandler or something at the very edge of the application.

There are so many cases in which you want to just log an Exception and tell the client(which is not always a human) to kindly try again or go away.

This will alarm a lot of people who are of the opinion that an App must fail as gracefully as possible. But failing hard and fast has greater returns and it’s cheaper to implement (as counter intuitive as it might sound).

Just by documenting your Exceptions won’t facilitate the most graceful termination possible, it will only give you a false sense of security and achievement. You documented the Exception. Everything is fine. Time for a well deserved smoke.

And that is how we end up with tens of Exceptions in docblocks of Tests and Controllers and the App still explodes. Everything is fine. All Exceptions are documented, everywhere.

Prepare yourself for push back if you ever try to adopt this. I heard somone lobby against this proposal because: ‘The IDE automatically documents all the Exceptions, if we accept this proposal we will have to think about it, if we think about it we might get it wrong’.

But you should think about it (even at the risk of getting it wrong). The Exceptions you throw, document and catch will heavily influence how things interact inside your App and that is something you should think and care about.

I will end this section with an example. If some low level code throws a DatabaseNotFoundException, it’s not like every Object that somewhere down the line, uses/writes smth to the DB (which is pretty much any Object in the Apps I work on) needs to either handle or document the DatabaseNotFoundException. That is bonkers.

Catching Exceptions

Now some of you reading this, might think that the solution to all I’ve been talking until now is each Layer of the App catching a plethora of Exceptions specific to that Layer, grouping them by some sort of logic and throwing a more narrow selection of Layer specific Exceptions.

An oversimplification of that approach is grouping all Exceptions that have some relation to the Database into a SmthIsWrongWithTheDbException.

The Layer above it will catch a plethora of PreviousLayerExceptions (including the SmthIsWrongWithTheDbException) and group it into a SomethingIsWrongBellowMeException.

Depending on how many Layers your Application has, it should be easy enough to guess where this is going.

This approach solves the ‘more docblock than code’ and ‘we are thinking about our code’ problem. But it rarely solves the ‘does it make sense problem’.

If at the end of the Request/Response cycle there isn’t an enough number of different outcomes (made possible by this orthodox grouping of Exceptions) then it does not make sense and if it does not make sense then you should not do it.

If after all that grouping, the end client (which is not always a human) sees or would be content to see the same output as in the case in which that low level Exception would have bubbled up directly in the ErrorHandler or some other edge of your App, then it does not make sense and you should not do it.

If the case in which an Exception is thrown must never reach Production, then we should not spend our time dressing it up in case it does. We should spend our time making sure it does not reach Prod.

Catching very general Exceptions is something that should be reserved for the edge of your app (or module sometimes). Transaction handling? Maybe, but ideally the ORM is taking care of that for you.

Catching \Throwable, \Exception or \Error (yes in PHP Errors can be caught) everywhere or being in a constant race to catch every Exception of your low level dependencies just so, God forbid, it does not bubble outside your module/layer is probably wrong and more importantly you are potentially obfuscating a major problem that should not be swept under the rug.

You should be stingy with your catches and write them with confidence and intent and not out of fear. It’s ok for the application to fail hard and fast, preferably before it reaches production.

At the end of the day I’m happy if I made you at least think about something you were doing on auto pilot and never thought about.

What other things you are doing without ever considering if does make sense to do them or not?

Special thanks go to Alexander who made me think about this and so many other things.

That’s it! Thanks for reading!

Disclaimer: Consider this to be a living document, which means it’s subject to undocumented changes and it might even die in the future.

--

--

Victor Todoran

I write, read and think about software and its users for a living