Re: Exception as the primary error handling mechanism?

2010-01-03 Thread Michi
On Jan 1, 2:47 pm, Peng Yu  wrote:
>
> In the article API Design Matters by Michi Henning
>
> Communications of the ACM
> Vol. 52 No. 5, Pages 46-56
> 10.1145/1506409.1506424http://cacm.acm.org/magazines/2009/5/24646-api-design-matters/fulltext
>
> It says "Another popular design flaw—namely, throwing exceptions for
> expected outcomes—also causes inefficiencies because catching and
> handling exceptions is almost always slower than testing a return
> value."
>
> My observation is contradicted to the above statement by Henning. If
> my observation is wrong, please just ignore my question below.

Seeing that quite a few people have put their own interpretation on
what I wrote, I figured I'll post a clarification.

The quoted sentence appears in a section of the article that deals
with efficiency. I point out in that section that bad APIs often have
a price not just in terms of usability and defect rate, but that they
are often inefficient as well. (For example, wrapper APIs often
require additional memory allocations and/or data copies.) Incorrect
use of exceptions also incurs an efficiency penalty.

In many language implementations, exception handling is expensive;
significantly more expensive than testing a return value. Consider the
following:

int x;
try {
x = func();
} catch (SomeException) {
   doSomething();
   return;
}
doSomethingElse();

Here is the alternative without exceptions. (func() returns
SpecialValue instead of throwing.)

int x;
x = func();
if (x == SpecialValue) {
doSomething();
return;
}
doSomethingElse();

In many language implementations, the second version is considerably
faster, especially when the exception may be thrown from deep in the
bowels of func(), possibly many frames down the call tree.

If func() throws an exception for something that routinely occurs in
the normal use of the API, the extra cost can be noticeable. Note that
I am not advocating not to use exceptions. I *am* advocating to not
throw exceptions for conditions that are not exceptional.

The classic example of this are lookup functions that, for example,
retrieve the value of an environment variable, do a table lookup, or
similar. Many such APIs throw an exception when the lookup fails
because the key isn't the table. However, very often, looking for
something that isn't there is a common case, such as when looking for
a value and, if the value isn't present already, adding it. Here is an
example of this:

KeyType k = ...;
ValueType v;

try {
   v = collection.lookup(k);
} catch (NotFoundException) {
   collection.add(k, defaultValue);
   v = defaultValue;
}
doSomethingWithValue(v);

The same code if collection doesn't throw when I look up something
that isn't there:

KeyType k = ...;
ValueType v;

v = collection.lookup(k);
if (v == null) {
collection.add(k, defaultValue);
v = defaultValue;
}
doSomethingWithValue(v);

The problem is that, if I do something like this in a loop, and the
loop is performance-critical, the exception version can cause a
significant penalty.

As the API designer, when I make the choice between returning a
special value to indicate some condition, or throwing an exception, I
should consider the following questions:

 * Is the special condition such that, under most conceivable
circumstances, the caller will treat the condition as an unexpected
error?

 * 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.

Apart from the potential performance penalty, throwing exceptions for
expected outcomes is bad also because it forces a try-catch block on
the caller. One example of this is the .NET socket API: if I do non-
blocking I/O on a socket, I get an exception if no data is ready for
reading (which is the common and expected case), and I get a zero
return value if the connection was lost (which is the uncommon and
unexpected case).

In other words, the .NET API gets this completely the wrong way round.
Code that needs to do non-blocking reads from a socket turns into a
proper mess as a result because the outcome of a read() call is tri-
state:

 * Data was available and returned: no exception

 * No data available: exception

 * Connection lost: no exception

Because such code normally lives in a loop that decrements a byte
count until the expected number of bytes have 

Re: Exception as the primary error handling mechanism?

2010-01-04 Thread Michi
l 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.
-- 
http://mail.python.org/mailman/listinfo/python-list


Re: Exception as the primary error handling mechanism?

2010-01-06 Thread Michi
On Jan 5, 9:44 am, Steven D'Aprano  wrote:
>
> I'm glad we agree on that, but I wonder why you previously emphasised
> machine efficiency so much, and correctness almost not at all, in your
> previous post?

Uh… Because the original poster quoted one small paragraph out of a
large article and that paragraph happened to deal with this particular
(and minor) point of API design?

> If all you're argument is that we shouldn't write crappy APIs, then I
> agree with you completely.

Well, yes: the article was precisely about that. And the point about
exception efficiency was a minor side remark in that article.

> Your argument seems to be
> that we should avoid exceptions by default, and only use them if
> unavoidable. I think that is backwards.

I never made that argument. After 25 years as a software engineer, I
well and truly have come to appreciate exceptions as a superior form
of error handling. I simply stated that throwing an exception when
none should be thrown is a pain and often inefficient on top of that.
That's all, really.

> I wouldn't say that's normal. If you don't care about the function's
> result, why are you calling it? For the side-effects?

printf returns a value that is almost always ignored. And, yes, a
function such as printf is inevitable called for its side effects. We
could argue that printf is misdesigned (I would): the return value is
not useful, otherwise it would be used more. And if printf threw an
exception when something didn't work, that would be appropriate
because it fails so rarely that, if it does fail, I probably want to
know.

> > 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.
>
> There's that premature micro-optimization again.

Let's be clear here: the entire discussion is about *inappropriate*
use of exceptions. This isn't a premature optimisation. It's about
deciding when an exception is appropriate and when not. If I throw an
exception when I shouldn't, I make the API harder to use *and* less
efficient. The real crime isn't the loss of efficiency though, it's
the inappropriate exception.

> I've been wondering when you would reach the conclusion that an API
> should offer both forms. For example, Python offers both key-lookup that
> raises exceptions (dict[key]) and key-lookup that doesn't (dict.get(key)).
>
> The danger of this is that it complicates the API, leads to a more
> complex implementation, and may result in duplicated code (if the two
> functions have independent implementations).

Offering a choice in some form can be appropriate for some APIs. I'm
not advocating it as a panacea, and I'm aware of the down-side in
increased complexity, learning curve, etc. (BTW, the article discusses
this issue in some detail.)

> Well, obviously I agree that you should only make things be an exception
> if they actually should be an exception. I don't quite see where the
> implication is

In the context of the original article, I argued that throwing
exceptions that are inappropriate is one of the many things that API
designers get wrong. To many people, that's stating the obvious. The
number of APIs that still make exactly this mistake suggests that the
point is worth making though.

Anyway, some of the early posts implied that I was arguing against
exception handling per-se because exceptions can be less efficient. I
responded to correct that misconception. What the article really said
is that throwing an exception when none should be thrown is bad API
design, and inefficient to boot. I stand by that statement.

Cheers,

Michi.
-- 
http://mail.python.org/mailman/listinfo/python-list


Re: ANNOUNCE: Ice 2.0 released

2004-12-08 Thread Michi Henning
> Does the Ice team claim any advantages to their Python bindings to
> CORBA over omniORBpy (The one I am currently using). [...]
> But I was wondering if there are any dynamic language
> oriented improvements in ICE bindings?

The Ice Python mapping is simpler than the CORBA one because Ice has
a simpler object model with fewer data types. So, Ice  avoids the
complexities caused by things such as type Any, TypeCodes, value types,
bounded sequences, arrays, unions, the fixed type, unsigned versus
signed types, and wide versus narrow characters and strings. Other than
that, the Ice Python mapping is quite similar to the CORBA one, mainly
because, for most language mapping issues, the design problems and (sensible)
solutions are the same.

As far as dynamic improvements are concerned, Ice for Python is a bit more
flexible than omniORBpy. As with omniORBpy, you can put interface definitions
through a compiler to generate stubs and skeletons but, in addition, you can
also use Ice for Python without precompiling the interface definitions and,
instead, load them at run time. As an example, assume you have a
definition as follows:

module M {
enum Color { red, green, blue };
};

Instead of compiling the definition, you can write:

Ice.loadSlice("Color.ice")
import M

print "My favourite color is ", M.Color.blue

Which approach (precompiled or dynamically loaded) you use depends
on the specifics of your application. Dynamically loaded definitions
are useful especially for things such as test harnesses and short throw-away
programs, because not having to compile the definitions first reduces
complexity
and makes the application more compact (because it doesn't depend on a bunch
of additional files that would be created by pre-compiling the definitions).
Of course, the price you pay is a (small) delay during start-up because the
code is generated at run time rather than compile time.

Cheers,

Michi.

--
Michi Henning  Ph: +61 4 1118-2700
ZeroC, Inc.http://www.zeroc.com

-- 
http://mail.python.org/mailman/listinfo/python-list


Re: ANNOUNCE: Ice 2.0 released

2004-12-09 Thread Michi Henning
On Thu, 9 Dec 2004, Duncan Grisby wrote:

> In article <[EMAIL PROTECTED]>,
>  Michi Henning <[EMAIL PROTECTED]> wrote:
>
> [...]
> >Instead of compiling the definition, you can write:
> >
> >Ice.loadSlice("Color.ice")
> >import M
> >
> >print "My favourite color is ", M.Color.blue
>
> Just like this then?
>
> omniORB.importIDL("Color.idl")
> import M
>
> print "My favourite color is ", M.blue


Oops, my apologies! I wasn't aware that omniORBpy does this
as well.

Cheers,

Michi.

--
Michi HenningPh: +61 4 1118-2700
ZeroC, Inc.  http://www.zeroc.com

-- 
http://mail.python.org/mailman/listinfo/python-list