On Jan 1, 2:47 pm, Peng Yu <pengyu...@gmail.com> 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 been read, the control flow because really awkward because the successful case must be dealt with in both the try block and the catch handler, and the error condition must be dealt with in the try block as well. If the API did what it should, namely, throw an exception when the connection is lost, and not throw when I do a read (whether data was ready or not), the code would be far simpler and far more maintainable. At no point did I ever advocate not to use exception handling. Exceptions are the correct mechanism to handle errors. However, what is considered an error is very much in the eye of the beholder. 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. Cheers, Michi. -- http://mail.python.org/mailman/listinfo/python-list