Re: Exception as the primary error handling mechanism?



On Jan 4, 1:30 pm, Steven D'Aprano
<ste...@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx> wrote:

This is very true, but good APIs often trade-off increased usability and
reduced defect rate against machine efficiency too. In fact, I would
argue that this is a general design principle of programming languages:
since correctness and programmer productivity are almost always more
important than machine efficiency, the long-term trend across virtually
all languages is to increase correctness and productivity even if doing
so costs some extra CPU cycles.

Yes, I agree with that in general. Correctness and productivity are
more important, as a rule, and should be given priority.

(For example, wrapper APIs often require additional
memory allocations and/or data copies.) Incorrect use of exceptions also
incurs an efficiency penalty.

And? *Correct* use of exceptions also incur a penalty. So does the use of
functions. Does this imply that putting code in functions is a poor API?
Certainly not.

It does imply that incorrect use of exceptions incurs an unnecessary
performance penalty, no more, no less, just as incorrect use of
wrappers incurs an unnecessary performance penalty.

But no matter how much more expensive, there will always be a cut-off
point where it is cheaper on average to suffer the cost of handling an
exception than it is to make unnecessary tests.

In Python, for dictionary key access, that cut-off is approximately at
one failure per ten or twenty attempts. So unless you expect more than
one in ten attempts to lead to a failure, testing first is actually a
pessimation, not an optimization.

What this really comes down to is how frequently or infrequently a
particular condition arises before that condition should be considered
an exceptional condition rather than a normal one. It also relates to
how the set of conditions partitions into "normal" conditions and
"abnormal" conditions. The difficulty for the API designer is to make
these choices correctly.

In some, limited, cases you might be able to use the magic return value
strategy, but this invariably leads to lost programmer productivity, more
complex code, lowered readability and usability, and more defects,
because programmers will invariably neglect to test for the special value:

I disagree here, to the extent that, whether something is an error or
not can very much depend on the circumstances in which the API is
used. The collection case is a very typical example. Whether failing
to locate a value in a collection is an error very much depends on
what the collection is used for. In some cases, it's a hard error
(because it might, for example, imply that internal program state has
been corrupted); in other cases, not finding a value is perfectly
normal.

For the API designer, the problem is that an API that throws an
exception when it should not sucks just as much as an API that doesn't
throw an exception when it should. For general-purpose APIs, such as a
collection API, as the designer, I usually cannot know. As I said
elsewhere in the article, general-purpose APIs should be policy-free,
and special-purpose APIs should be policy-rich. As the designer, the
more I know about the circumstances in which the API will be used, the
more fascist I can be in the design and bolt down the API more in
terms of static and run-time safety.

Wanting to ignore a return value from a function is perfectly normal
and legitimate in many cases. However, if a function throws instead of
returning a value, ignoring that value becomes more difficult for the
caller and can extract a performance penalty that may be unacceptable
to the caller. The problem really is that, at the time the API is
designed, there often is no way to tell whether this will actually be
the case; in turn, no matter whether I choose to throw an exception or
return an error code, it will be wrong for some people some of the
time.

This is a classic example of premature optimization. Unless such
inefficiency can be demonstrated to actually matter, then you do nobody
any favours by preferring the API that leads to more defects on the basis
of *assumed* efficiency.

I agree with the concern about premature optimisation. However, I
don't agree with a blanket statement that special return values always
and unconditionally lead to more defects. Returning to the .NET non-
blocking I/O example, the fact that the API throws an exception when
it shouldn't very much complicates the code and introduces a lot of
extra control logic that is much more likely to be wrong than a simple
if-then-else statement. As I said, throwing an exception when none
should be thrown can be just as harmful as the opposite case.

It doesn't matter whether it is an error or not. They are called
EXCEPTIONS, not ERRORS. What matters is that it is an exceptional case.
Whether that exceptional case is an error condition or not is dependent
on the application.

Exactly. To me, that implies that making something an exception that,
to the caller, shouldn't be is just as inconvenient as the other way
around.

 * Is it appropriate to force the caller to deal with the condition in
a catch-handler?

 * If the caller fails to explicitly deal with the condition, is it
appropriate to terminate the program?

Only if the answer to these questions is "yes" is it appropriate to
throw an exception. Note the third question, which is often forgotten.
By throwing an exception, I not only force the caller to handle the
exception with a catch-handler (as opposed to leaving the choice to the
caller), I also force the caller to *always* handle the exception: if
the caller wants to ignore the condition, he/she still has to write a
catch-handler and failure to do so terminates the program.

That's a feature of exceptions, not a problem.

Yes, and didn't say that it is a problem. However, making the wrong
choice for the use of the feature is a problem, just as making the
wrong choice for not using the feature is.

Apart from the potential performance penalty, throwing exceptions for
expected outcomes is bad also because it forces a try-catch block on the
caller.

But it's okay to force a `if (result==MagicValue)` test instead?

Yes, in some cases it is. For example:

int numBytes;
int fd = open(...);
while ((numBytes = read(fd, …)) > 0)
{
// process data...
}

Would you prefer to see EOF indicated by an exception rather than a
zero return value? I wouldn't.

Look, the caller has to deal with exceptional cases (which may include
error conditions) one way or the other. If you don't deal with them at
all, your code will core dump, or behave incorrectly, or something. If
the caller fails to deal with the exceptional case, it is better to cause
an exception that terminates the application immediately than it is to
allow the application to generate incorrect results.

I agree that failing to deal with exceptional cases causes problems. I
also agree that exceptions, in general, are better than error codes
because they are less likely to go unnoticed. But, as I said, it
really depends on the caller whether something should be an exception
or not.

The core problem isn't whether exceptions are good or bad in a
particular case, but that most APIs make this an either-or choice. For
example, if I had an API that allowed me to choose at run time whether
an exception will be thrown for a particular condition, I could adapt
that API to my needs, instead of being stuck with whatever the
designer came up with.

There are many ways this could be done. For example, I could have a
find() operation on a collection that throws if a value isn't found,
and I could have findNoThrow() if I want a sentinel value returned.
Or, the API could offer a callback hook that decides at run time
whether to throw or not. (There are many other possible ways to do
this, such as setting the behaviour at construction time, or by having
different collection types with different behaviours.)

The point is that a more flexible API is likely to be more useful than
one that sets a single exception policy for everyone.

As the API
creator, if I indicate errors with exceptions, I make a policy decision
about what is an error and what is not. It behooves me to be
conservative in that policy: I should throw exceptions only for
conditions that are unlikely to arise during routine and normal use of
the API.

But lost connections *are* routine and normal. Hopefully they are rare.

In the context of my example, they are not. The range of behaviours
naturally falls into these categories:

* No data ready
* Data ready
* EOF
* Socket error

The first three cases are the "normal" ones; they operate on the same
program state and they are completely expected: while reading a
message off the wire, the program will almost certainly encounter the
first two conditions and, if there is no error, it will always
encounter the EOF condition. The fourth case is the unexpected one, in
the sense that this case will often not arise at all. That's not to
say that lost connections aren't routine; they are. But, when a
connection is lost, the program has to do different things and operate
on different state than when the connection stays up. This strongly
suggests that the first three conditions should be dealt with by
return values and/or out parameters, and the fourth condition should
be dealt with as an exception.

Cheers,

Michi.
.



Relevant Pages

  • Re: Exception as the primary error handling mechanism?
    ... and reduced defect rate against machine efficiency too. ... example of an API that is simply poor: ... exception than it is to make unnecessary tests. ... more complex code, lowered readability and usability, and more defects, ...
    (comp.lang.python)
  • Re: Exception as the primary error handling mechanism?
    ... In the article API Design Matters by Michi Henning ... In many language implementations, exception handling is expensive; ... the caller will treat the condition as an unexpected ...
    (comp.lang.python)
  • Re: Exception as the primary error handling mechanism?
    ... reduced defect rate against machine efficiency too. ... Does this imply that putting code in functions is a poor API? ... exception than it is to make unnecessary tests. ... The caller has to remember to check the return result for the magic ...
    (comp.lang.python)
  • Re: MiniDumpWriteDump
    ... I changed the structure to use pointers instead of using the types inline ... Now, when an exception occurs, and is caught my me (see sample code in same ... (Please forget my last posting's question about whether the API can access ... >>> End Sub ...
    (microsoft.public.vb.winapi)
  • Re: Exception as the primary error handling mechanism?
    ... In many language implementations, exception handling is expensive; ... the normal use of the API, the extra cost can be noticeable. ... the caller will treat the condition as an unexpected ... C# also misses Python's trick of giving string ...
    (comp.lang.python)