At a fairly high level, a system's response to detecting an exception is limited to only a few choices. The way that a system responds should depend on the kind of exception and the intended target. I use three different types of exceptions. I have seen a classification that used four, but I don't recall what they were.
The three that I use are:
- programming logic errors
- resource failures
- user errors
Programming Logic Error exceptions occur when a program determines that it has achieved a state which should not be possible, such as accessing the tenth element of a five element array or attempting to pop an element from an empty stack. These are generally caught via assertions and tend to render some portion of the system unusable. The audience for this exception is the development staff. The user cannot do much when one of these is thrown, except to try avoiding the buggy functionality and report the problem to the developer. This means that the program doesn't need to be able to distinguish these exceptions from each other, and the message displayed to the user needs to be understandable only by the developers (beyond a general, "You have encountered a bug - sorry about that - please report it" header).
Resource failure exceptions occur when some resource outside of the control of the program (such as memory, disk space, or a peripheral) is not available when needed. In this case, the user *can* do something about the problem, which should be temporary. He can close other programs, clear disk space, plug in the network cable, etc. This means that the message associated with the exception must be understandable by the user (and should be translated if the program is localized). The program may also be able to run in a partially degraded mode, and therefore needs to be able to distinguish among the kinds of exceptions to know which resource is no longer available.
A user error occurs when a user attempts to do something that is not supported by the system and not directly prevented by the user interface. It may be that such an error should not actually result in an exception, but should be handled by the program (this is a style issue); however, an error message does not need to be generated to the user. The program may also be able to help by guessing at what the user was attempting. The biggest drawback to using exceptions in this fashion is that it makes detecting multiple errors difficult.
As far as I can tell, all exceptions should fit in one of these categories. Can anyone see something I have overlooked?
I'd include configuration errors, in which a configuration file is invalid. Since the person who configures a system may not be a programmer or user, the other classifications don't quite fit. You can also have data integrity errors, when, for example, data in a database doesn't satisfy expected constraints.
Sometimes exceptions don't signify an error at all. For example, I wrote a program that searched a directory hierarchy, and it would sometimes run into unreadable files. Trying to process these files would cause an exception, but the program handled the exception by skipping the files. -- RalphJohnson
Maybe this is a style issue. If I expected some unreadable files in such a case, and considered it normal for this to happen, and of no particular interest to the user, I would prefer to test each file for readability before trying to read it and getting an exception. That is, I would not consider unreadable files to be exceptional, and therefore not appropriate for causing exceptions. A problem with using exceptions here is that their timing characteristics are not guaranteed in most languages; thus, it tends to be slower to cause an exception and handle it than to avoid it by testing for a condition that could fail. Of course, sometimes this is unavoidable - the API may simply not be written so as to provide a means of determining readability. In that case, you have no alternative but to handle the expected exception.
We also need to consider whether the existence of unreadable files should be hidden from the user. If not, we would say that it *is* an error for their to be unreadable files in the search space, but the program can handle this case w/o user intervention. In that case, though, I would hope that the program could at least keep a log of the files it was asked to search but couldn't.
This is definitely a style issue. BjarneStroustrup observes in the 3rd edition C++ manual that exceptions should mainly be reserved for exceptional conditions rather than control flow, but that exceptions "can be an elegant technique for terminating [...] highly recursive search functions such as a lookup in a tree". So far as I'm concerned, if you're going to throw an exception then you should be quite clear on where there is a responsibility to catch it, and document same. So long as you do that, and you bear efficiency in mind, determining what is and is not exceptional is not terribly important.
An example of this pattern is terminating a chess program's recursive search when the time allotted has run out. This can also be expressed in C with setjmp/longjmp if one can "unwind" any state changed by the recursive procedure at the longjmp destination. The smell for using this pattern is an abort flag or return code within the recursive procedure.
Exceptions are, in my opinion, a better choice for the file iteration case because the file being readable at the time of the test does not guarantee that the file is readable when it is actually opened. The file is under the control of the OS and may at any time have its read access changed. So, even within a synchronized block (without locking the file via the OS), the program cannot ever guarantee that the file will be readable at a later point in the program's execution. (Additionally, many libraries cache file statistics which furthers the problem.)
I'm not sure where inverting a degenerate matrix would come in with this classification. Although you could make non-degeneracy a pre-condition and test for it before trying to invert it, in practice it's quicker to try and see if it works, and fail if it doesn't. It's not really an external resource problem, or a logic error program. I believe things like this are fairly common in numerical work.
With discussions like this, I'm not always clear on whether we're talking about "exceptions" the language feature, or exceptional cases however they be handled. Most of the comments apply equally well to a routine which returns an error result code for failure.
"Exceptions - the language feature" is based on the notion that it's better to separate the error-handling code from the normal flow of control. It's supposed to make the normal flow easier to understand. This theory isn't about whether the error cases can be detected in advance.
In Java, we can declare exceptions that must be caught or passed on explicitly - they can't be ignored. If we don't agree with the flow-of-control philosophy, maybe what we really want is a way of requiring that a routine result not be ignored. I'm sure I've heard about languages that did this (early Pascal?) for all functions, which was irritating, but maybe it would be worth having selectively, for those functions that need it. -- DaveHarris
This discussion is
intended to be about "exceptions - the language feature." When my co-workers and I first started using exceptions (in Ada), we tended to over-use them. For example, we would read a file by looping until EOF was reached - which would throw an exception. In retrospect, this was a poor way to write code.
- When performance is important, exceptions are a killer, as the time to throw and catch them is generally not well-defined, nor is it a prime consideration to compiler writers.
- Sure, don't use a lot of exceptions in tight loops. Also don't do I/O there, etc. Optimization is a different issue to design.
- What if the exceptional condition happens one time in a million? An exception might take a comparatively long time to handle, but it would save you a million comparisons and conditional branches.
- Exceptions are not very obvious as a flow-control mechanism: a while loop with a specified termination condition is easier for the next programmer to read than an infinite loop with an exception.
- Often, exceptions are unobvious. For terminating recursion, however, as Bjarne points out, they're often a lot more obvious than the various magic variables that are the alternatives. HorsesForCourses.
- When debugging, it is very convenient to tell the debugger to stop when an exception is thrown. If exceptions are used as described above, almost every throw is the result of something gone wrong (which is why you are debugging, after all).
Note that it is not relevant to try to detect logic errors in advance. When one is encountered (via an assertion) the program is erroneous.
Resource errors are a completely different matter. -- RussellGold
Apart from the recursive instance, the other tempting place to use exceptions as control flow is as an observer alternative. I wonder which is more obvious/less efficient? Anyone really tried to benchmark these things?
When performance is important, exceptions are a killer...
And why is this so? Most programming platforms (or better: compiler implementors) seem to embody an incredibly slow
exception mechanism, and justify it with the presumtion that exceptions shall be thrown in exceptional
conditions only. And what? Is that to say, they could have done it better, they just didn't pay much attention to it?
Then, there are the implementors of the standard library for the language. When they implement routines like, say, parsing a string into an integer, they naturally throw exceptions when the string does not denote a number. They do it all over the libraray. Like in the case of matrix inversion, there are many many many cases when it is easier (and computationally more efficient) to try and fail, then to check a priori.
This gives rise to such absurd situations, when, say, there is no other way to check if user input is a valid integer but try to convert it, and pay for every "No" answer with a performance penalty - sometimes a factor of a couple of 1000!
Some languages/compilers/platforms do recognize this fact and implement exception handling in a very performant way. Delphi and Python (and, to my knowledge, CommonLisp
) are excelent examples of this. But the majority seem to stick to the ridiculousness of "ehm, exceptions just have
to be exceptional, you know..." -- ZoranIsailovski?