The Right Approach to Exceptions

Jonathan M Davis jmdavisProg at gmx.com
Sun Feb 19 17:18:30 PST 2012


On Sunday, February 19, 2012 17:35:31 Andrei Alexandrescu wrote:
> On 2/19/12 4:20 PM, Nick Sabalausky wrote:
> > "Andrei Alexandrescu"<SeeWebsiteForEmail at erdani.org>  wrote in message
> > 
> >> The wheel is not round. We just got used to thinking it is. Exceptions
> >> are
> >> wanting and it's possible and desirable to improve them.
> > 
> > They're wanting? What's the problem with them? I see no problem, and I
> > haven't seen you state any real problem.
> 
> I mentioned the issues here and there, but it's worth collecting them in
> one place.
> 
> 1. Type multiplicity. Too many handwritten types are harmful. They
> exacerbate boilerplate and favor code duplication.

Assuming that the exception provides additional information via member 
variables rather than just being a new exception type which is essentially 
identical to Exception save for its type, then you have to write them by hand 
anyway. It's only the ones where their only point of existence is their type 
(which therefore indicates the type of problem that occurred but isn't able 
for whatever reason to give any further useful information) which result in 
boilerplate problems. And since in general we really should be adding member 
variables with additional information, the boilerplate should be minimized. 
And if we _do_ want such classes, we can use a mixin to generate them (the 
downside being that they don't end up in the ddoc - though that could be fixed, 
and it probably should be).

We shouldn't go to town and create tons and tons of exception types, but we 
should also have them where they're useful.

> 2. Using types and inheritance excessively to represent simple
> categorical information.

try-catch blocks are _designed_ to take advantage of types and inheritance. 
So, moving away from using derived types for exceptions makes it harder to 
write exception handling code. It also works very well with inheritance to 
have someone catch a more general exception and ignore the existance of the 
more specific ones if they don't care about the specifics of what went wrong. 
So, the hierarchy ends up being very useful. And how do intend to _add_ 
additional information to exceptions if not through derived types which can 
then hold additional member variables? I don't see how you could have the 
relevant information without going out of your way to avoid inheritance by 
using type codes and something equivalent to void* for the data. It would just 
be cleaner - and better suited to how catch blocks work - to use an exception 
hierarchy.

It seems to me that the very design of exceptions in the language is geared 
towards having a class hierarchy of exceptions. And I contend that doing that 
works quite well. Certainly, in my experience, it does. It's when programmers 
try to avoid handling exceptions that things go awry (e.g. by catching 
Exception and then ignoring it), and that can happen regardless of your 
design. You can't prevent programmers from being stupid.

> 3. Forcing cross-cutting properties into a tree structure. The way I see
> exceptions is a semantic graph, and making it into a tree forces things
> like repeated catch clauses for distinct types coming from distinct
> parts of the hierarchy.

Except that in most cases, you want to do handle exceptions differently if they 
come from different  parts of the hierarchy. And making it so that you could do 
something like

catch(Ex1, Ex2 : Ex e)

would cover the other cases quite well.

> 4. Front-loaded design. Very finely-grained hierarchies are built on the
> off chance someone may need something AND would want to use a type for that.

There's definitely some truth to that. However, it's very easy to add more 
exception types later without disrupting code.

For instance, if you added IOException, and made all of the exsisting 
exception types which would make sense with that as their base class then 
derive from it, then new code could choose to call either IOException or the 
more specific exception types. And existing could would _already_ call the more 
specific exception types, so nothing would be disrupted.

And if you added more specific exceptions (e.g. creating FileNotFoundException 
and making it derive from FileException), then new code could choose to catch 
the more specific exception instead of FileException, but existing code would 
continue to catch FileException without a problem.

So, we can organize our exception hierarchy on a fairly rough level to begin 
with and add exceptions to it (both common and derived) as appropriate. And 
while we'd still end up with some up front design, because we'd have to create 
at least some of the hierarchy to begin with, the fact that we can add to the 
hierarchy later without breaking code minimizes the need to add stuff that we 
think that we _might_ need.

It's removing exception types and moving them from one part of the hierarchy 
to another which is disruptive. And while avoiding those _does_ mean doing a 
good job with how you initially put exceptions into a hierarchy, I don't think 
that it's that hard to avoid once you've got a basic hierarchy, especially if 
we start with a minimal number of exception types and add more later as 
appropriate rather than creating a large, complex hierarchy to begin with. The 
main breakage would be in moving from the module-specific exceptions to a 
hierarchy, and much of that wouldn't break anything, because as we don't have 
much of a hierarchy right now, it would only be an issue with the ones that we 
decided to remove.

> 5. Unclear on the protocol. The "well-designed" phrase has come about
> often in this thread, but the specifics are rather vague.

As with any design, we'd have to look at concrete examples and examine their 
pros and cons. I think that looking at C# and Java is a good place to start. 
That should give us at least a good idea of what _they_ think is a well-
designed hierarchy, and we can take the pieces that best apply to us and our 
situation. We don't need to start with anything as large as they have, but 
it's something to work from.

> 6. Bottom-heavy base interface. Class hierarchies should have
> significant functionality at upper levels, which allows generic code to
> do significant reusable work using the base class. Instead, exceptions
> add functionality toward the bottom of the hierarchy, which encourages
> non-reusable code that deals with specifics.

True. But I don't see a way around that. The more specific the problem, the 
more information that you have. The more general the problem, the less 
information that you have. So, you naturally end up with more data in the 
derived classes. And for the most part, it's the data that you want, not 
polymorphic functions. Polymorphism is of limited usefulness with exceptions. 
It's the hierarchy which is useful. In fact, you could probably have 
exceptions be non-polymorphic if we had a construct in the language which had 
inheritance but not polymorphism. But we don't. So, while I think that you 
make a valid point, I still think that using classes with an inheritance 
hierarchy is the best fit.

- Jonathan M Davis


More information about the Digitalmars-d mailing list