Re: Exception as the primary error handling mechanism?
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?
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?
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
> 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
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