Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Jeremiah Lee
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Jeremiah Lee ( @JeremiahLee )

Exceptions Are For Exceptional Circumstances And Thoughts On Cross-Layer Coupling

By on

In any conversation about Exceptions and Error Handling, someone is bound to throw out the phrase, "Exceptions are for exceptional circumstances." To me, this kind of guidance sounds good, but adds no value to the discussion. It's like telling people, "You gotta live life!" It sounds good; but, its meaning is purely subjective. That said, I've been noodling on errors a lot lately and it seems to me that this idea about "exceptional circumstances" creates tight coupling between the layers of your application.

As a thought experiment, imagine that we have a service layer and a data-access layer. And, we have some workflow that retrieves an entity from the data-access layer in order to process it:

// In some service LAYER....

function doSomething( id ) {

	if ( ! repository.entityExists( id ) ) {

		return( /* Error response ... somehow. */ );

	}

	// If we made it this far, the entity should exist. Were it NOT to exist, that
	// would be a truly exceptional situation - let's get it so that we can act on it.
	var item = repository.getEntity( id );

	// ... do something with it.

}

// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //

// Meanwhile, in the Repository LAYER....

function getEntity( id ) {

	var entity = this.cache[ id ];

	if ( ! entity ) {

		throw( new Error( "EntityNotFound" ) );

	}

	return( entity );

}

In this thought experiment, we're checking to see if the entity exists before we retrieve it. This is akin to the "Tester-Doer Pattern" as outlined by Microsoft. And, I believe that the test-before-access means that if the access fails, the "exceptions are for exceptional circumstances" people would have to agree that this case is truly "exceptional." I mean, after all, we took all the precautions that we could in order to not make erroneous requests.

But, here's the problem with that thought - the repository doesn't know that the service layer is checking for entity existence before calling .getEntity(). In fact, the data-access layer doesn't even know that the service layer exists. As such, the .getEntity() behavior can't change based on changes in the service-layer - doing so would create very tight coupling in the wrong direction. This means that the .getEntity() method has to always throw an error (on not-found) regardless of whether or not the service layer performs the existence check prior to access.

Of course, I'm sure you could argue that, by its very nature, a repository is a volatile container, so it is always expected that an entity may not exist, and therefore it is not truly exceptional. But, at some point, you could say that about anything. In JavaScript, you can change the Prototype of an object at any time; so, should you consider it "not exceptional" if you go to invoke a prototype method that's been deleted? That seems weird, right?

If nothing else, this whole thought experiment just reenforces my belief that the concept of "exceptional" is far too subjective to be helpful in a practical discussion about error handling. I'm still strongly in the "exceptions are good" camp. But, that's not to say that I always want to throw exceptions. Sometimes returning a Falsey makes an API easier to consume - such as when accessing undefined properties on an object. To me, considering ease-of-consumption is an order of magnitude more informative than debating about what "exceptional" actually means for your application.

Want to use code from this post? Check out the license.

Reader Comments

31 Comments

Exceptions are pretty subjective, but my take on the code here is that the throw is in the wrong place. Your repository assumes that the non-existence of an object is an application-stopping event. But why should it? It was asked to find something, that thing wasn't there, so the repository communicated that in some way back to the consuming service.

Imagine this:

Customer to salesman: "Do you have any of these shoes in my size in the back?"
Salesman doesn't find any
Salesman: "I'm sorry, we don't"

OR

Customer to salesman: "Do you have any of these shoes in my size in the back?"
Salesman doesn't find any and burns the store down

15,674 Comments

@Matt,

An exception doesn't necessarily indicate an "application stopping" event - just that the method couldn't what it said it could do. The next layer up could certainly catch the exception and deal with it (if possible).

But, the point of the post was that if the code is attempting to prevent the exception by calling .exists() first, then the lack of the entity was "exceptional" from the point-of-view of the control flow. Similar to check to see if a Struct contains a key - structKeyExists() - before trying to access the property (whose nonexistence would cause an error).

To take your analogy, I see it more like you buy the shoes, they give you the box, then when you get home you find out that the box is empty - WTF? ... then you go back and burn the place down :D

That said, I've been going over the HTTP component - in ColdFusion - in my head. The HTTP component never throws an error (well, you can ask it to, but it's off by default). And, I really like this behavior. So, what sets it apart from the something like asking an entity gateway for an entity?

The conclusion that I come to is that the HTTP component has a symmetric response object. By that, I mean that "success" and "failure" responses come back in the same format. It's not like the Success responses returns the "fileContent" and the Failure response returns the "HTTP Result" -- they _both_ return the HTTP Result. That's what makes it different - there is no room for ambiguity of what the response means.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel