Object Oriented Design — Exceptions

Jon Acker
6 min readJan 6, 2019

--

There seems to be a common misunderstanding around the use of Exceptions in OOP which I’d like to try and clear up, and this goes back to what I wrote earlier about how we think when coming from the procedural world (such as C or PHP circa 2001), vs how we should think within the OO paradigm.

In old fashioned procedural design, the most common way to deal with errors is to check the return value of a function. For example, a function may return a value (such as an integer) from a computation if no error occurs, but might return something exceptional such as the value false or -1 when something has gone wrong. This is very common to see in languages such as C.

One of the problems with this approach is that we end up with functions that have an inconsistent interface, or signature. In PHP it commonly looks like this:

/**
* @return string|false
*/
public function process(string $value)
{
if ($value === '') {
return false;
}

// some something with string

return $processedString
}

In addition to preventing us from providing a return type for the function, it forces us to write client code that has to check the return value for false in case of an error — we can’t simply return the value from the function.

OO languages provide a more sophisticated approach which, for one thing, allow us to always return the same predetermined type from a function, and for another allow us to skip the client code that checks for errors. Which brings me to the common mistake made by programmers coming from a procedural background.

And exception is not an error. In fact, exceptions in OOP are better thought of as events. As most of you probably know, an event is something that interrupts the normal flow of a program but is explicitly dispatched using an event bus or dispatcher. In the event-dispatch paradigm (especially the asynchronous variety) we are encouraged to simply “fire & forget” — the event handler or listener which is registered for that particular event will catch it and deal with it.

Well, exceptions are essentially the same sort of thing — they are a signal that is meant to be dispatched when something has gone wrong with our computation. Object Oriented languages have a built-in mechanism for propagating events up through the layers of the application in much the same way an event bus would carry an event to its handler or listener. Exceptions, like events, are meant to be fired & forgotten. (Well, not completely forgotten, as we’ll shortly see).

When learning layered architectural design, programmers are often taught to throw an exception that is relevant to the layer that generated the error. This is correct in a general sense, because if a an something goes wrong within the process of a particular layer, you’ll generally want to know which layer this error came from. And even though an exception is meaningful only inside the layer within which it is generated, the handling of this exception need not occur until it has reached an outer layer or its’ intended recipient —at a point at which an action needs to be taken based on the exception, which it can be given a new meaning based on its original one.

If you are throwing an exception and immediately catching it within the calling class simply in order to transform it into a new exception of that calling class, then you are missing the whole point of exceptions and not taking advantage of your OO languages’ built-in exception mechanism (the equivalent of its’ event-bus for exceptions).

Let’s take a look at an example. Imagine we have a simple HTTP client:

As you can see, this client throws an HttpException when the curl request results in an error. Now, imagine that the class using this client is some kind of Library Catalog class that needs to fetch a list of books from a remote API.

This Catalog class is a consumer of the Client class. It might potentially be interested in the exception thrown by the Client — but in this case it isn’t. The Catalog does some additional processing of its own on the list returned by the Client and might event throw its own exception. But it does not care about exceptions thrown by the Client, nor does it need to act on them.

Now if you look at this class as presented by a modern IDE, such as PhpStorm, you’ll notice that the IDE is trying to draw your attention to the fact that the Client throws an exception which the Catalog is not handling:

The IDE offers you two ways of dealing with this:

  1. Surround with try/catch (and re-throw the exception with another that is specific to the Catalog)
  2. Add a @throws tag to the method annotation

If we choose the 1st option simply in order to transform the HttpException into a CatalogListingException, we are falling back into the procedural paradigm. Nobody cares that this used to be an HttpException and is now a CatalogListingException unless this was a processing error that happened inside the Catalog class itself.

But since this error did not occur inside the Catalog, and our Catalog is not in the slightest bit interested in the exception thrown by the Client — we should choose the 2nd option.

But “hang on”, you say. Where do we catch this exception? In order to answer that question we need to ask “who cares?”. In other words, does the layer above (the framework) care? Perhaps it needs to convert this exception into a proper 500 error response? In which case, the framework should catch it:

But perhaps this isn’t a web application and not even the framework cares about this exception. In which case maybe we want to catch it in the main application script? In which case the Kernel doesn’t need to catch the exception either:

And now, the smart IDE tells us the the main script has an unhandled exception:

Which we solve by surrounding the handle method with a try/catch. We only catch the exception here in order to tell the user that something went wrong:

That is — we don’t catch the exception until the very last opportunity, so that at least the user of this command-line app will receive a decent-looking error message when something goes wrong.

And maybe… maybe we don’t even care about providing a decent error message to the user and are happy to just bomb-out with an exception, which will anyway show what went wrong, as part of PHP’s default exception handling. In which case — we don’t need to catch the exception anywhere!

In this case — we really do “fire & forget” the exception.

In summary — you only need to catch the exceptions generated if you care about them at the higher levels, even when they are generated deep down in the belly of the beast. This the true advantage of OO exceptions over its procedural counterpart of returning error codes.

--

--

Jon Acker
Jon Acker

Written by Jon Acker

Software Engineer, BDD advocate

No responses yet