Amazon.com Widgets On the Proper Design of Exception Hierarchies

On the Proper Design of Exception Hierarchies

By Nick at February 19, 2012 13:13
Filed Under: Delphi

Introduction

Exceptions are all over the place in Delphi.  Introduced way back in Delphi 1.0, they totally changed the way that folks looked at error handling.  Previously, we’d do things like:

ErrorCode := DoSomething;
if ErrorCode > 0 then 
begin
  DoNormalStuff;
end else
begin
  UhOh;
end;

We had to constantly interrupt the flow of our code in order to make sure that all is well.  Exceptions changed all that by letting you put all the error handling at the end, in a try…except block – out of the way of the flow of your code -- where you could handle things separately, leaving you to worry about how things should go, and not how they might go wrong.

Way back for Borcon 2004, I wrote a paper on exception handling called “Exception Handling for Fun and Profit”.  You can read that and get a good idea on the proper way to handle exceptions.  It is eight years old, but it’s still all good advice.

What I want to talk about here, though, is the best way to write your own exception classes and exception class hierarchies.  The Runtime Library gives you a number of Exception types, and it uses most of them.    You are, of course, free to use any of those exceptions declared in SysUtils.pas and elsewhere, and of course you can simply create an exception from the base class Exception. But most often when writing your own library code, the standard exception classes are not enough.  So I’m going to try to give you some ideas on how you can and why you might create your own exception classes.

A note before I start: Exception hierarchies should be part of library code; that is, code you write for use by other applications. As noted in my aforementioned article on exceptions, application developers should trap exceptions, and library code writers should raise exceptions. Exception hierarchies are part of developing library code, so this article really only pertains to that side of the fence.

The Basics

The first thing I’ll tell you is that you should never raise a plain exception:

raise Exception.Create(‘An error occurred’);

I can’t think of any reason for ever doing that.  It provides no useful information about the type of exception and why the exception being raised, leaving consumers of your code with little helpful information.

So, as you’ve probably noticed,  I like to say “You should never do <this>”.  And of course, when I do that, it’s incumbent on me to say what you should do instead.  So here goes.

First, you should always raise a specific exception for a specific exceptional situation.  If it’s a “normal” exception, use on of the standard Exception classes that are part of the RTL.  For instance, if you are dealing with streams, and you have an error, you might raise an EStreamError

Most often, though, you want your exception to be as precise and exact as possible.  This way, it will be easier to pinpoint exactly what went wrong when the exception is captured and displayed.  And in order to do that, you should create your own exception hierarchy.  If an existing exception type isn’t perfect for the situation, then don’t be afraid to define one that is.

And when you do that, the hierarchy you create should be flat and wide.  Flat and wide exception hierarchies allow for the simple organization of similarly purposed exceptions.   Here’s what I mean by that.

Flat and Wide

I’ll use my class library THTMLWriter as an example.  It it, there is a unit called HTMLWriterUtils.pas that contains the following code:

type
  EHTMLWriterException = class(Exception);
    EEmptyTagHTMLWriterException = class(EHTMLWriterException); // Tested
    EOpenTagRequiredHTMLWriterException = class(EHTMLWriterException); //Tested
    EHeadTagRequiredHTMLException = class(EHTMLWriterException); // Tested
    ETryingToCloseClosedTag = class(EHTMLWriterException); // Tested
    ENotInListTagException = class(EHTMLWriterException); // Tested
    ENotInTableTagException = class(EHTMLWriterException); // Tested
    ENotInCommentTagException = class(EHTMLWriterException); // Tested
    ENotInFieldsetTagException = class(EHTMLWriterException); // Tested
    ENoClosingTagHTMLWriterException = class(EHTMLWriterException);
    ENotInFrameSetHTMLException = class(EHTMLWriterException); // Tested
    ENotInMapTagHTMLException = class(EHTMLWriterException); // Tested
    ENotInFormTagHTMLException = class(EHTMLWriterException); // Tested
    ENotInObjectTagException = class(EHTMLWriterException); // Tested
    EClosingDocumentWithOpenTagsHTMLException = class(EHTMLWriterException); // Tested.
    ETableTagNotOpenHTMLWriterException = class(EHTMLWriterException); // Tested
    EParamNameRequiredHTMLWriterException = class(EHTMLWriterException); // Tested
    ETagIsDeprecatedHTMLWriterException = class(EHTMLWriterException); // Tested
    ENotInSelectTextHTMLWriterException = class(EHTMLWriterException); // Tested
    ECaptionMustBeFirstHTMLWriterException = class(EHTMLWriterException); // Tested
    EBadTagAfterTableContentHTMLWriter = class(EHTMLWriterException); // Tested
    ENotInDefinitionListHTMLError = class(EHTMLWriterException); // Tested
    ECannotNestDefinitionListsHTMLWriterException = class(EHTMLWriterException); // Tested
    ECannotAddDefItemWithoutDefTermHTMLWriterException = class(EHTMLWriterException); //Tested

This is an example of a “flat and wide” exception hierarchy.  The hierarchy is “flat” because there are only two levels of inheritance – the base class and a large number of descendants.  A class diagram of these classes would be “flat” because there isn’t much distance from the top to the bottom of the inheritance.  It’s “wide” because there are a lot of sibling classes on the second level of inheritance.  The class diagram would take a lot of horizontal space if it were laid out on paper.

How does this all work?  First, I declared a base type, EHTMLWriterException.  All the exceptions raised in the THTMLWriter library will be a descendant of this type.  As a result, you can easily check the type of an exception, and if the type is EHTMLWriterException, then you know what library raised the exception.  When you are using the THTMLWriter library, you can easily trap all the exceptions raised by the library within your exception handling code by simply calling:

try
  //code that raises an exception in THTMLWriter....
except
  on E: EHTMLWriterException do
    //you know that the exception came from THTMLWriter
end;

Second, I declare a large number of very specific descendants that are raised in very specific situations.  Their names are very descriptive, and they are usually raised only in one place – the exact place that the error occurred.  This makes locating the cause of the exception much easier.   Precisely named exceptions can be easily searched for in code.

An Example

For instance, here’s an example of some code calling a very specific exception:

function THTMLWriter.OpenParam(aName: string; aValue: string = cEmptyString): IHTMLWriter;
begin
  CheckInObjectTag;
  if TStringDecorator.StringIsEmpty(aName) then
  begin
    raise EParamNameRequiredHTMLWriterException.Create(strParamNameRequired);
  end;
  Result := AddTag(cParam)[cName, aName];
  if TStringDecorator.StringIsNotEmpty(aValue) then
  begin
    Result := Result[cValue, aValue];
  end;
end;

Here you can see that the code raises an exception named EParamNameRequiredHTMLWriterException.  The class name alone tells you that it is an error from the THTMLWriter library, and that it was raised because a parameter name was required but not found when the call was made.  Just the exception’s class name has a lot of information.  In addition, you’ll note that when I raise these specific exceptions, I include a very specific, detailed error string explaining what happened.  In my view, you can’t have enough information in an error message. 

Thus, if you end up raising this exception, you’ll get this:

image

Given all that, if you can’t figure out exactly what the problem is and exactly where it occurred, well, then I’m not sure you are in the right business.  Winking smile

Lots and Lots of Exception Types

Since it is fine – and even desirable – for exception hierarchies to be wide, you shouldn’t be shy about declaring many exception classes as direct descendants of your one main exception class.  You’ll note above that even in a small library of code like THTMLWriter, I’ve declared 23 exception types.  I see nothing wrong with having a large, very specific set of exceptions that you can raise throughout your code library.  As I said before, if your exception type is only raised once, that’s fine – it’s very easy to find the location of an exception that is only raised in one place. 

Conclusion

So there it is – make your exception class hierarchies flat and wide, use good exception messages when raising them, and you’ll enable the consumers of your code to easily see where problems are arising and to quickly deal with them when they do.

blog comments powered by Disqus

My Book

A Pithy Quote for You

"When buying and selling are controlled by legislation, the first things to be bought and sold are legislators."    –  P. J. O'Rourke

Amazon Gift Cards

General Disclaimer

The views I express here are entirely my own and not necessarily those of any other rational person or organization.  However, I strongly recommend that you agree with pretty much everything I say because, well, I'm right.  Most of the time. Except when I'm not, in which case, you shouldn't agree with me.