Unexceptional examples

We’re going to run out of amusing titles for these exception related blog entries sooner or later…

Joel wants Ned to rename the functions in his ’exceptions are better’ example. He wants InstallSoftware(), CopyFiles() and MakeRegistryEntries(). Jesse Ezell leapt in with a C# version which includes the rollback functionality that Joel was undoubtedly hinting at. The thing is, although Jesse is defending the use of exceptions, I think his example is making the case for the other side…

I spend most of my time in C++ land. We have the best exceptions. ;) They fit well with our constructors and deterministic destructors. We can use the Resource Acquisition Is Initialisation idiom (RAII) to write classes that manage the lifetime of resources and make them safe in a world of exceptional exits. We don’t need many try {} catch blocks because, if we write it right, our code just does the right thing when an exception passes through. Our exceptions are unchecked by the compiler and this is a Good Thing. This is the land where Joel is dead wrong in his belief that exceptions are like gotos and that error returns are better.

If Joel’s error returns are a hammer then our exceptions are a power screwdriver. When used correctly, with the correct parts of the rest of our toolbox our power screwdriver is considerably more powerful and elegant than a hammer. The problem is that most of the people using it seem to just hold it by the end and swing it at their nails. I’ve also seen people who advocate error returns saying that we’re not comparing ’like with like’ if we try and prove that exceptions, used in the right way, are better than error returns. I’m sorry guys, you can’t compare ’like with like’ because if you just replace your error returns with exceptions and try and structure the code in pretty much the same way then you’re just missing the point entirely.

So how would I show that exceptions are superior to error returns? Something like this I guess:

void DoInstall()
{
   CFileInstaller fileInstaller;
   CRegistyInstaller regInstaller;

   fileInstaller.Install();
   regInstaller.Install();

   fileInstaller.Commit();
   regInstaller.Commit();
}

The ‘installer’ classes would follow an ‘undoable command’ pattern. They’d have an Install() and a Commit() and a Rollback() method and they’d operate much like database transactions do. You call Install() which does the work and as long as you call Commit() they don’t call Rollback() when they are destroyed. Install() is allowed to throw, Commit() isn’t.

Those of you at the back that just stood up and started shouting about how my little rules about which functions can throw and which can’t? Sit down, shut up, live with it. Effective use of exceptions sometimes requires these little guarentees. All Commit() should do is set a boolean flag to true. In C++ we could choose to enforce our ‘can’t throw’ rule with a throw specification on the function - note: throw specs are a pretty broken feature of C++ so don’t use them for anything except specifying a no throw situation…

For those of you unfamiliar with C++’s destructors there’s a ‘hidden’ call to ~CRegistryInstaller() and ~CFileInstaller() just before the closing brace. The destructors can check to see if the action was committed and if not call Rollback(). The destructors are guaranteed to be called just before the closing brace and they’re called in the opposite order to the order you constructed the objects.

One of the callers can handle any exceptional situations because they’ll know what to do; all we know is that either both installations work or both fail.

My example scales; though if you get many more installation components you’d probably want to add them to some form of ‘Installation Stack’ object so that you can do something like this:

{
   CInstallerStack installer;

   CFileInstaller fileInstaller(installer);
   CRegistyInstaller regInstaller(installer);
   COtherInstaller reg2Installer(installer);
   COtherInstaller reg3Installer(installer);
   COtherInstaller reg4Installer(installer);
   COtherInstaller reg5Installer(installer);
   COtherInstaller reg6Installer(installer);

   installer.Install();
   installer.Commit();
}

Each installer adds itself to the supplied stack in it’s constructor, you call Install() on the stack and it walks its list of installers in order and calls Install() on them. It then walks its list and calls Commit(). Object destruction takes care of any rollbacks required as above.

In C# I guess you’d use IDisposable and Using with the stack based solution to achieve the same goal. With Java you’d need a try {} finally but that’s just because they don’t trust you.

I can provide source code to the above in C++, C# and Java if you really want.

So, in summary; Exceptions when used correctly with the idioms and parts of the language that support them are clearly superior ;) to error returns. They can be misused, or used as a replacement for error returns and that’s less effective.

Thanks Joel, I’ve enjoyed the discussions that broke out all over the place. I’ve agreed with lots of people, I’ve disagreed with lots of people; I’ve questioned my own code and my reasons and I’ve been forced to think about things. Now, can we move on?