Reputation: 67213
In .Net:
1) Do you let the exception propagate to the global error handler where you both log the error and show a msg box? The reason I ask is if you use a single global error handler to catch exceptions at the top, you'd need a bunch of Ifs to deduce the exception type and then show a message which may be friendly (e.g. for filenotfound, the file you provided was not found, etc).
I have a global error handler in my winforms app. For any exceptions I cannot handle, they hit this handler where it logs the exception and displays a message - but this is the exception message, sometimes I may want something a little different depending on the audience etc.
2) I only catch exceptions I can handle. However, my application has a set of folders in a user-selectable path. As the default share is locked down and audited etc, any missing files are rare and therefore an "exceptional event". But when the user selects a location on his or her own pc, files can be missing, directory names too long, etc. I think this is a lot more common so don't wat to use exceptions here. But then earlier I mention I did. For this sort of conflict, is there an alternative way to structure my code without duplication?
3) Lastly, there are some exceptions which I cannot handle (e.g. a file does not exist, this cannot be handled because the files are special format and only uploaded by certain IT staff). but this is rare. So for these exceptions in a method, I don't catch them. However, when someone looks at this (despite the comment that this is caught at the global level), other developers shudder. Is this down to their ignorance?
Lasstly, what does it mean to unwind the stack trace?
Thanks }
Upvotes: 1
Views: 164
Reputation: 81105
Exceptions should be raised when, and only when, either (1) conditions exist where the routine promises to raise exceptions, or (2) the routine is not capable of satisfying the post-conditions that it would promise for a successful return. It would in many cases be possible to specify the post-conditions for a routine loosely enough that it would be almost guaranteed not to throw except in case of stack overflow, thread abort, or other such disaster, but in many cases it is more helpful to offer a tighter guarantee.
Consider, for example, the task of reading 'n' bytes from a file. It's possible to write a routine to read up to 'n' bytes from a file, and return the number of bytes read (indeed, that would be the usual paradigm absent exceptions). Trying to parse a file with such a routine can be annoying bother, though. Read up to 4 bytes, see if four bytes were read, and if so, convert them to a long, read up to that number of bytes, see if all expected bytes were read, etc. Having to test after every read to see if it successfully got all the bytes expected quickly gets to be a chore and it obscures what the code is supposed to do. An alternative approach is to have a routine promise that it will only return if it successfully read as many bytes as were requested and throw an exception otherwise. In such a scenario, the tighter promise in the byte-read routine eliminates the need for tests within the code ensure that the read completed fully and successfully.
The primary benefit of exceptions lies is felt in cases where a failure of any of a large number of function calls to meet an expected post-condition should result in the same error-handling behavior. If it's necessary to have special behavior when one particular function call doesn't behave as expected, it's generally more convenient to simply have a routine return an indication of whether the behavior was expected or unexpected, and look at that result. If, however, one will make dozens of function calls and won't especially care which one of them yields unexpected behavior, using exceptions will avoid the need to error-check each one.
Upvotes: 0
Reputation: 47699
To some degree your questions depend on your specific operating environment and language (which you hint at but never disclose), but to a degree the issues are quite generic. (Unfortunately, this is also an area of lots of disagreement, both between the various operating environments and between individual developers, sometimes on the same project, so what I say below may need to be modified for your context.)
Having worked with exceptions for over 30 years, I've seen just about every exception mechanism and style there is, including some that were very primitive and some overly "cute". There are some common principles, though:
First, one needs to define what an exception is. For our purposes here let's say its a notification of an unusual condition that is related to the code currently executing (rather than, say, a condition on an I/O device the current code is not using). And let's say it's delivered synchronously relative to the current executing code, not delayed (except maybe for a few machine cycles), and not (initially) sent to unrelated code.
Continuing in this vein, we assume that the currently executing program has a (at least conceptual) call-return stack, representing the order that calls have been nested from the beginning of program execution up to the point of the exception. Since this is the case, and since exceptions are "related" to the currently executing code (eg, a divide-by-zero is due to a division operation in the current procedure), there is likewise a relationship of sorts with each procedure in the call-return stack. That is, each procedure "knows" that that exception resulted, directly or indirectly, from its own execution and the procedures it called.
What does this say? Well, usually the procedure closest to the error (ie, the one on "top" of the call-return stack) best "knows" what the cause of the error is, and is best suited to handle it. But sometimes (eg, "file not found" exception) the procedure executing closest to the point of the exception doesn't have sufficient knowledge to handle the error (because, eg, it has no idea where it's caller got the non-existent file's name, or what the contents of the file were supposed to represent). For this reason, if the current procedure cannot handle the error it should (implicitly or explicitly) "resignal" the error to the calling procedure (and so forth down the call-return stack, until a procedure that wants to handle the error is found). Most exception schemes do this "resignal" automatically, though some require the programmer to manage it to a degree.
Eventually, if the error isn't handled, you get to the bottom of the call-return stack. This is when the default handler takes over and decides what to do (kill the program, resume at some "safe" restart point in the code, etc). The default handler also will likely attempt to log the error (and the call-return stack contents) so that it can be debugged later.
So (absent any reasons not to due to your specific operating environment), the best way to handle exceptions is in the individual procedures that best "know" what they mean. In some cases a procedure may only know what an exception means in a generic sense (eg, for some reason that divide-by-zero means "invalid weight") so it may resignal with a new, program-defined exception type, expecting that its caller (or its caller's caller...) will "know" what "invalid weight" means and how to handle it).
Many people believe that exceptions should only be used to handle "disastrous" errors that will necessarily result in at least a nasty error message to the user, if not a complete program termination, but that view is quite wrong. It's perfectly fine to use exceptions to manage, say, invalid user input, and, in fact, it's generally better to use exceptions, from a "structured programming" standpoint, than to use return codes and the like. The only caveat would be that in some environments exceptions are painfully slow, and should not be used where they may occur with considerable frequency. But most newer environments are more "enlightened" and in them exceptions can be used with little concern for performance.
One does need to change ones programming style slightly when using exceptions. Most importantly, care must be given to structuring the code such that the start/end markers for an exception handling range correspond to the start and end of the conceptual operations related to that possible exception, and one must be sure that any "backout" code needed to undo a partially completed operation in the event of an exception is appropriately connected to that exception range (eg, with a "finally" clause, or, where the language does not support that, as a separate subroutine that can be invoked from the exception handler for the exception range). But once you begin to understand this sort of thing, error recovery becomes much, much simpler.
In the general scheme of things, it's perfectly OK to not handle exceptions that can't be handled, and let the default handler take care of them. However, in some cases it may be desired to, eg, close files or release resources. Sometimes this can be done from the default handler, but in other cases it is better handled using individual exception handlers (generally set up to catch "any" exception) which simply do the required cleanup operations and then resignal the original exception. Exceptions allow you to do this near to the place in the code where the resource was allocated in the first place, so that there is no need for the default handler to contain a big list of all the allocated resources and know how to release them all. This technique can dramatically reduce bugs due to resources not released.
"Unwinding a stack (trace)" could have a few different meanings, but generally, when an exception occurs in procedure A but is rippled back through the call-return stack to A's caller B and then to B's caller C, say, and C's exception handler "handles" the exception (does not resignal it to C's caller), there is a need to restore the call-return stack to the state it was in when C was executing, so that C can continue with "normal" (more or less) execution. To do this, the entries for A and B on the stack would be (at least conceptually) "popped" or "unwound". Sometimes (depending on the system/language) this "unwinding" occurs as the "ripple" of the unhandled exception occurs, but sometimes it's not done until the exception is marked "handled". Normally you shouldn't need to concern yourself with this, but some systems or some special cases may require knowing about it.
It's also useful to note that most systems make the contents of the call-return stack at the time of an exception available somehow for logging as debug information. This is extraordinarily useful, especially when debugging "remote" errors, and it's worthwhile to find out how the mechanism works on your system.
Upvotes: 1