April 7, 2016

...Learn TDD with Codemanship

Exceptions Are Not Events

Something I catch myself doing - and should really know better - is committing the sin of using exceptions as events in my code.

For example, I recently threw together a basic Java unit testing framework to test how long it might take as a mini-project for SC2016 (we're still keen to hear you ideas for those, BTW). Look at how the assertion mechanism works:

When an assertion fails, it throws a TestFailureException with an optional message, which is caught be the class responsible for invoking tests dynamically through reflection.

If running the test causes an exception - of any kind - to be thrown, then the test has failed, and the reasons for that failure are reported.

Now, the reason I used this mechanism is because I was lazy. How do I wire up callbacks on an implicit method invocation, without making the listener a parameter of every test method or test fixture constructor? "I know. I'll just throw an exception when it fails."

In actual fact, this is how some xUnit implementations really do it.

But let's be clear; this isn't what exceptions are for.

An exception should be thrown when something has gone wrong with our program. Tests failing isn't "something going wrong". Tests can fail: that's part of the normal functioning of the testing tool. A test failing is an event, not an exception.

Had I wanted to invest a little bit more time, I could have used the Observer pattern (and, indeed, did as much when I redid this exercise in C#.)

The rule of thumb for exceptions should be as follows:

If it's part of the normal functioning of your code, then it's not an exception.

If your ATM user interface allows cardholders to select an amount to withdraw that's not actually available, we don't handle them selecting unavailable amounts by throwing an exception. Your application allows that input, and should handle it meaningfully.

It pisses me off no end when edge cases like these are handled with exceptions. Displaying an error message when the user has done something your design let them do is poor UX etiquette. If need be, check that input, and if it's not valid, raise an event that has a proper event handler, all part of the UX flow.

Better still, don't offer the user the opportunity to perform invalid actions or choose invalid options. If £10 notes aren't available, don't offer them the choice of withdrawing £10, £30 or £50. An InvalidWithdrawalAmount exception is just wasting their time.

One of the best summaries of what exceptions are really for, and how our code should properly handle them is Bertrand Meyer's Discipline Exceptions. Read it with your eyes, and then learn it with your mind.

I'm off to do some much-needed refactoring...

Posted 4 years, 6 months ago on April 7, 2016