mercredi 18 février 2009

Throw those error codes out

What to answer when asked to justify your use of exceptions:
  • It makes the main flow of the program obvious
  • It's the only sane way to signal an error from a constructor
  • It avoids partially constructed objects
  • You can't avoid them anyway
  • The information carried by exceptions is better structured than error codes
  • Error codes don't scale, exceptions do
It makes the main flow of the program obvious
When using exceptions, you don't need to check every single function call for error condition. This removes a lot of repetitive, useless code that gets in the way when trying to understand what is the first purpose of a single piece of code.

It's the only sane way to signal an error from a constructor
You could choose to only have useless constructors and init methods on every class... and you'd be wrong to do it. init methods are often the result of an inadequate error management strategy. More on that later, for now it should be clear that constructors don't have return values, and if you give a constructor an output parameter, you are in a fight against the language you can't win.

It avoids partially constructed objects
This one is huge! People underestimate it - big time. A good deal of what makes a class difficult to design & maintain is the number of states its instances can have. If any member of a class can be either constructed and non-initialized, constructed and initialized, constructed and partially initialized, constructed and uninitialized and , finally, destroyed.... what was I saying? Oh yeah, if any member can be in any one of those states, you are in trouble. You get destructors that are impossible to write, you have live objects everywhere in your system that are time bombs. Ever seen code like this?




if ( is_init() )
{
do_it();
}
else
{
return E_FAIL;
}


That means that the object can be in an invalid state. The class is not doing its job : the class's invariant is ill-defined. You don't get that when you use constructor with exceptions.

It all makes sense when you consider how members initialization/finalization works. First, members are destroyed in the reverse order of construction : it makes sense. When members are constructed {that's before the body of the parent's constructor begins its execution}, if the Nth member's constructor throws, the previous members {those that are fully constructed} will see their destructor called : it makes sense. If the Nth members fails, the destructor of the following members won't be called : that is very useful.

And memory leaks? If you have this :

my_object* obj = new my_object();


and if the constructor of my_object throws, the memory allocated by new will be freed automatically! No leak! :-) Of course, if would be a good idea to use some kind of smart pointer because if this object's constructor won't fail maybe something else will throw before the call to delete.

You can't avoid them anyway
new throws. The standard library throws. Boost throws. The world throws. Accept it. You not throwing does not spare you from writing exception-safe code.

The information carried by exceptions is better structured than error codes
It's easier to choose the right exception class when throwing than to structure error codes returned by functions. You will find tons of functions returning an int as an error code, I hope you will find a lot less functions throwing an int :-)

Error codes don't scale, exceptions do
Error code management implementation size grows with the number of functions called. Exception catching is pretty much O(1) and exception throwing implementation is proportional to the number of possible failed postconditions, I don't see how you could do better than that.

Shit happens. For the love of code, throw the shit out.

Aucun commentaire: