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