Boost logo

Boost :

From: Weston Markham (wmarkham_at_[hidden])
Date: 2003-11-05 21:17:27


Peter Dimov wrote:

> Approximating "right" by polling and taking the opinion of the majority
> should only be applied in cases where objective measurements cannot provide
> a (socially acceptable) answer. This is not the case here. There are several
> fairly simple rules of thumb that can be used; most of them are best stated
> in a negative form, i.e. what not to do.
>
> * Don't force users to catch something that they do not need to handle.
> * Don't force users to write switch statements if a chain of catch handlers
> will do.
> * Don't force users to write a chain of catch handlers in situations where a
> single catch will do better.
> * Don't use unproven assertions about public/LWG opinion as justification
> for the design.
> * In case of conflicting priorities, do tailor the design for what the
> majority of users _do_. Don't be affected by what they _say_ they would do,
> could do, or what they like. Don't be affected by what they do but need not
> (the "unpythonic" rule ;-) ).

I would like to try to add a concrete example of why I agree with
these rules of thumb. (And, just in case it really is a popularity vote, to
make sure that my vote counts!) It has been a while, however, since
I have done any work with sockets. Please let me know if my attempt
to be concrete fails by not making any sense within that domain, so
that I try to work out a better example.

If users write (or are "forced" to write) client code like:

try
{
    ...
}
catch( const socket_base::bind_failure& e )
{
    if( e.reason() == socket_base::already_bound )
        ...
}
catch( const socket_base::connect_failure& e )
{
    if( e.reason() == socket_base::connection_refused )
        ...
}

...then an author of a new protocol's implementation will have difficulty in
expressing any exceptional condition that is particular to that protocol,
in a way that fits within the established framework.

Suppose that the new protocol in question can fail at "connect" time for
a number of reasons. I'll call one of these reasons "nonexistent_port",
and another one "insufficient_resources". Both of these involve
communication with a remote host that refuses the connection.

So, in order for the above code to continue to work as intended, these
conditions must be indicated by the implementation throwing, in both
cases, some instance of connect_failure that returns connection_refused
as its reason().

Perhaps "insufficient_resources" is a condition that is fairly transient in
nature. A reasonable strategy for handling it might be to wait a few
seconds and retry. "nonexistent_port", however, might indicate some
sort of configuration problem. Perhaps the client code ought to inform
the user and let them sort out how to deal with it. So, how can client
code that is aware of these new exception conditions distinguish
between them?

Even if the author of the new kind of socket is able to make changes to
the connect_failure class in order to support this new protocol, this leads
to a very ugly, non-OO design. Sockets are intended to abstract an
unspecified number of different underlying communication mechanisms.
So the details of one of these mechanisms shouldn't show up in the
general classes.

In this scenario, the only satisfactory solution is for the author to subclass
connect_failure. So, we are back to modeling exception conditions with
a class hierarchy, even despite our attempt to "minimize" the number of
exception classes.

There's also a "slippery slope" sort of argument: if a design goal is to
avoid "too many" classes, then where is the cutoff? Is 5 too many? 2?
1? I hope that it is clear that there is an inherent danger there. By
contrast, if one deliberately creates "too many" exception classes, (let's
create connection_refused_on_a_tuesday and
the_dog_ate_my_network_interface!) but fits them into some
meaningful hierarchy, the only problem is inventing names for them all!
(Well, I exaggerate, but hopefully my point is clear.) As a rule of
thumb, if an exceptional condition is a coherent enough concept that it
already has a name, then it seems to me that it is a good candidate for
an exception class.

Anyway, in short, I believe that a hierarchy of types provides a better
model for exceptional conditions than an enumeration of values.

I also, by the way, understand that sometimes market forces are even
more important than perfection. If, for some reason, potential users of
a library will actually refuse to use it because they are unable to write an
exception handler in the style that they like, then it is better to allow that
style then to leave the library unused. At worst, you can create an
enumeration that documents the known types of exception, but still
throw instances of more specific classes.


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk