October 17, 2003
@ 04:30 AM

After Joel Spolsky's recent post on exceptions which generated lots of dissenting opinions he has attempted to recant in a way that leads one to believe he is bad at accepting critical feedback.

In his most recent posting Joel writes

And Back To Exceptions

There's no perfect way to write code to handle errors. Arguments about whether exception handling is "good" or "bad" quickly devolve into disjointed pros and cons which never balance each other out, the hallmark of a religious debate. There are lots of good reasons to use exceptions, and lots of good reasons not to. All design is about tradeoffs. There is no perfect design and there is certainly never any perfect code.

His response is a cop out and not something I'd expect from a professional software developer who had been shown the errors in his proposed solution. This non-recant actually lowers my opinion of Joel in ways his original post did not. At least the original post could be based on past experience with bad uses of exceptions or usage of exceptions in programming languages where they were bolted on such as C++. His newest post just sounds like he is trying to save face instead of critically analyzing the dissenting opinions and showing the pros and cons of his approach and that of others. The best part is that he picks perhaps the worst defense of using exceptions to counterpoint against

With status returns:

STATUS DoSomething(int a, int b)
{
    STATUS st;
    st = DoThing1(a);
    if (st != SGOOD) return st;
    st = DoThing2(b);
    if (st != SGOOD) return st;
    return SGOOD;
}

And then with exceptions:

void DoSomething(int a, int b)
{
    DoThing1(a);
    DoThing2(b);
}

Ned, for the sake of argument, could you do me a huge favor, let's use a real example. Change the name of DoSomething() to InstallSoftware(), rename DoThing1() to CopyFiles() and DoThing2() to MakeRegistryEntries().

Since I'm always one to take a challenge here goes how I'd probably write the code

void DoSomething(string name, string location)
{

try{
    InstallSoftware(name);
    CopyFiles(location);
    MakeRegistryEntries(name);

  }catch(SoftwareInstallationException e){

  RollbackInstall(name);

  }catch(FileIOException fioe){

  RollbackFileCopy(location); 

  RollbackInstall(name);

 }catch(RegistryException re){

   RollbackRegistryWrite(name);

   RollbackFileCopy(location); 

   RollbackInstall(name);

  }

DisplayInstallationCompleteMessage(name, location);

}

or even better

void DoSomething(string name, string location)
{

    try{
    InstallSoftware(name);
    CopyFiles(location);
    MakeRegistryEntries(name);

   }catch(Exception e){

     throw InstallationException(e, name, location);

  }

DisplayInstallationCompleteMessage(name, location);

}

Of course, you could ask what happens if a rollback attempt fails in which case it either should catch the exceptions in itself or throw an exception. Either way, I prefer that the above approaches to littering the code with if...else blocks.