Simple Classes

(Kudos to Colin Kershaw for a wonderful IM session where we waxed philosophical on this topic -- virtual pairing!)

This entry is about Simple Classes: the utility of writing classes with little or no data or behavior. Like so many topics, I came to this one via a digression, but to explain that I need to digress...

I'm working on a proof of concept (POC) (that is also part spike). My goal is to model only one aspect of the system with enough context to make it meaningful.

This can be hard, because its easy (for me) to digress and add things to the model (i.e. 'simplified representation used to highlight relevant aspects', not 'object model') that are not strictly relevant. However, these often provide interesting design insights that -- while not strictly the point of the spike/POC -- may end up being just as useful to one's overall understanding of the solution. I had one of those a couple of days ago.

My POC/spike consists (in part) of the following:

Driver program (D) creates object (A) and gives it another object (B) that accesses an API. D then asks A to do something. Thus:

D creates an A that uses a B (via dependency injection, but that's not especially relevant here).

or put another way:

D sends a message (in the 'object' sense, not the 'messaging' sense) to A asking it to do something using B (thus B is a strategy object -- that's not relevant either) or in ASCII shorthand D -> A(B), where '->' is 'send message' and '()' is 'uses'.

My spike is exploring alternative B strategies, but as I said this post is about a digression (two actually): How should we handle exceptions raised by the API accessed by B? This is a digression since the pattern: D -> A(B) is not specific to my POC, but is a specific type of multi-level code call situation where the lowest level raises exceptions.

Now, we've all heard the exception handling maxim: "Exceptions should be handled at the lowest level where there is enough context for the application to handle them properly." but of course we need to know what "enough" and "properly" mean for our system to handle them... well properly.

Many organizations (and books) define these terms in a "standard" way resulting in two common (anti?) patterns: Exception handlers at the outer edge of the system only (or none at all!) and handlers in every method (which either simply rethrow them, or worse silently ignore them).

Now, I don't advocate either extreme, but rather (especially for a POC) that they communicate to me what's going wrong with my system in a way that's most helpful for me the programmer to understand what's going wrong.

Since any exceptions raised in the API mean I am using the API wrong, I first chose the most obvious solution: B will handle the exceptions and write something out to the console (D is a console app) so I can eyeball them and read more about the API.

To me, a key difference between a POC and a spike is the audience (POC is for others, while a spike is just for me to learn something). Thus, I want the design to reflect the real system, and also not seem to advocate any egregious design flaws (oversimplification is fine). On the face of it, the approach makes sense as B is the only code that knows about the API, and we want to encapsulate that; specifics of B's implementation shouldn't leak into the surrounding code. Like so many intuitive solutions though, this one doesn't work.

The problem is that while B knows about the API, it doesn't know what to do to recover when there is a problem -- in fact in the POC this is a security breach. D is asking A to encrypt itself using B (an implementation of a specific encryption algorithm). If an exception occurs in the underlying encryption API, B will fail to encrypt the data passed to it by A. A won't know this since its sending in plaintext and expecting ciphertext back. D of course now assumes A is encrypted, so it will send A on its journey (presumably a dangerous one that requires A to be protected), and the whole thing is a bust.

Even in the lesser case where B sends back nothing A now has a null value where before it had meaningful data -- we just lost something that can't be recovered, since A was replacing plaintext with ciphertext!

So, how should this be handled? Without changing the basic design of D -> A (B) and so changing the problem -- which remember is a general one and so may arise again even if this design is not the best way to encrypt A -- I see three options:

1) Modify B so that it somehow informs A that there will be no exceptions and B will return null if something bad happened (i.e. document this behavior so that A's designer knows to retain the information until it tests for null)

2) Create no error handling in B and let either: 2a) A handle it. 2b) D handle it

3) Wrap the exception and rethrow, again letting either: 3a) A handle it. 3b) D handle it

I feel option #1 is clearly bad as all it does is convert a meaningful error report into a true/false status code -- and relies on human intervention for its application (this is getting dangerously close to the religious war of error codes/vs exceptions -- so let's not go there shall we?), and also does nothing to inform D that A is not encrypted. This would require A to include some sort of Encrypted? attribute that D can test for (more status checking). While, this might be a useful property of A, it seems to have passed the buck to D -- but through a series of opportunities for passive programming errors (i.e. where one has to do nothing to introduce a bug). I like to minimize passive bugs, so I ruled this one out -- as did Colin.

This leaves us with two decisions to make: Should A or D handle the exception, and should B do any preprocessing of the exception before they get their shot at it.

Again, I think the first choice is an easy one. A doesn't know what alternative approaches to encryption are out there -- and that was a conscious design decision, so its up to D to figure out what to do (in the POC D just let's them bubble up to the screen).

So we are left with answering whether we go with option 2b or 3b. Again, Colin and I were in agreement: B should add context, and rethrow the exception. This has several benefits:

  • It clarifies B's intent: "You are on notice, I will be throwing exceptions!"
  • It provides an opportunity for B's to include specific information about what they were doing at the time of the exception: "I used these two settings, OK?"
  • It still provides us with an opportunity to isolate the rest of the system from the API
  • It provides a place to put the information (besides writing it out to the screen)

And so we come to the point of this entry: Simple Classes (remember?). In the POC this takes the form of a custom exception. Here's the class definition (example in C#):

public class EncryptionException : ApplicationException
{
    public EncryptionException(string message, Exception e) : base(message, e) {}
    public EncryptionException(string message) : base(message) {}
}

This class has no data, and no operations -- its just a marker. As Colin noted, a lot of people are reluctant to create such a do-nothing class (and not just for raising exceptions), but I want to talk about why I find them useful (hint: its all the benefits I gained going with option 3b in the POC):

First, they clarify intent. Simple classes such as these make an implicit concept in the code, explicit. While its true that the encryption API may raise a null reference exception, or an "invalid environment" exception, to the world beyond B these are all problems with encryption. Creating an "EncryptionException" doesn't create this idea -- it just documents it.

Second, they provide lodging for homeless data. Almost as soon as you create one of these guys, little bits of information will start jumping out at you, urging you to move them to a new home in the simple class, easing the housing situation whereever it currently resides (overcrowding is a constant problem for OO ecosystems). In the POC, B was writing that information to the screen making it hard to read inline (in production systems many programmers get the same result by writing it to a log file), but you might find relevant data anywhere; sitting in a field on an otherwise unrelated class, or even in a local variables and copy/pasted (ugh duplication) to other classes when needed. So, if you find ourself copying local variables from one function to the next, or sticking random data bits onto otherwise conceptually intact class definitions, my guess is your system is short on Simple Classes, creating one might be just the ticket.

Third, they facilitate system malleability. Especially when they inherit from an interface or a base class (as in the POC), they can assist with minimizing dependencies. Without the Encryption Exception class, I was left with two choices: Handle generic (Application)Exceptions or inform higher level code of the specific exceptions raised by the API -- requiring them to refernence the API. With one such refernence its not that big of a deal, but what if we had 5 different encryption algorithms? What if there were ten strategies? That's a lot of clutter (and dependency management) that can be eliminated by one "do-nothing" class. This use of simple classes is so widely beneficial that many languages (including C#) make it explicit by introducing the notion of interfaces. Intefaces are just simple classes where the language imposes a permanent immigration ban. So, by all means use interfaces, just remember that objects serve as more than just establishing type relationship (interaces establish the "is-a" relationship, as in, object A is a Foo, and the "uses" relationship as in object A uses a Foo), they also serve as locations of both data and behavior.

And this is what I want to wrap up with (something that's been on Colin's mind a lot lately): Its very easy to lose one of those two concepts along the way when desiging an object-oriented system: Lose the data and your classes start getting names like OperationManager, and take on a procedural quality. Lose the methods, and your code starts to look like a database (or shudder, a domain model).

The cool thing about Simple Classes is that by losing both data and methods (at least for a while) you regain what objects are really about: A concept.