First I'm curious: Are you referring strictly to
AppDomain.UnhandledException or to all three Global Exception Handlers? As
I was referring to all three as a whole.
As was I. BTW, there is only one true UE, the AppDomain.UE - the others are
actually events generated by different libraries that wrap execution threads
and generate the event when they catch an exception; typically the exception
gets swallowed. Some appear to use the subscription to the event as a
filter - if there is a subscription then the exception gets swallowed and
the event generated; if there are no subscriptions the exception propagates
up the call stack. It's been a while since I did the investigation and I
don't recall the details of which one does what.
Consider the flip side. How does the launcher know the app you are calling
has its own exception handling? I hope you don't think the writer of the
launcher can "blindly" trust all the apps being launched are correctly
written so it doesn't need any exception handling!
Of course the launcher does not make any assumptions whatsoever about the
code it launched - it has its own error handling to guard against buggy
apps.
One could argue that all apps should be small & simple, especially ones
that are meant to scale.
One could make that argument, but it's silly to do so. Large problems
require large solutions, and not all problems, or applications, can be
reduced to a small, trivial size. You can modularize it, componentize it,
etc., and these are all good practices, but the fact remains that the parts
must be assembled into a whole. This is especially problematic with
exceptions because you now have a system comprised of separately designed
and built components (perhaps in different continents) that must
interoperate with each other, yet the effects of an unhandled event (a UE)
may be felt far beyond the boundaries of that component. It is the
non-local nature of exceptions that makes them so powerful and also so
dangerous.
Perhaps we have a philosophical disagreement.
I regard an exception as an error condition and I regard an unhandled
exception as a programming error. This states it in absolute terms and I
will also state that there are always exceptions to this rule, but in most
cases I find this to be accurate.
All execution threads should have a exception handler somewhere, if only to
ensure proper reporting and propagation of the error, and all code paths
must be known and testable. It is also desirable to make the code as
readable and understandable as possible, if for no other reason then the
make it easier to spot defects during a code review - using UEs to
communicate results makes this harder.
That is not to say that all exceptions should be swallowed...the exceptions
should continue to propagate until a handler is reached to deal with the
particular exception - ultimately to deal with all exceptions. It is only
that thread that has sufficient context to make the most correct decision on
how to proceed (e.g. Abort/retry/cancel). A UE handler simply has no context
to evaluate the situation.
The exception mechanism is also a uni-directional channel for information to
flow up the call stack - it reports on errors and other exceptional
conditions. It is loosely typed and does not provide a signature against
which a compiler can enforce usage, other then a generic Exception type (and
even that is not guaranteed, as you can catch, and presumably throw,
non-Exception derived types).
Exceptions are not a resource that can be dealt with the way that memory or
other managed resources are treated; there is no execution GC to correctly
clean up after a faulted thread. It is way too easy to get cleanup details
wrong. Sure, you can use finally blocks (and you should) for
transaction-oriented cleanup, but these are not always guaranteed to run,
and even if they do, the code must still be proven to be correct, and often
it isn't. The situation gets even more complex and murkier when there are
numerous asynchronous threads and interop involved, when nested exceptions
occur, when aborts get injected into threads, multiple appdomains are
involved, remoting, partially completed methods, objects left in an
indeterminate state, etc. It gets further complicated when adding in the
effects of versioning and system evolution.
Exception handling is a very complex subject and it cannot be reduced to a
few paragraphs here. It is tightly bound up with execution and thread
control, performance, correctness, robustness, and reliability. It doesn't
help that the tools to analyze data flow and execution control in
conjunction with exceptions are practically non-existent - it must be
hand-analyzed, and we all know the pitfalls of that.
I believe that using UEs for error handling results in less reliable, less
robust systems; there will be a natural tendency to ignore most exceptions
because something else, somewhere else, will deal with it...and this will
usually be the wrong way to deal with it.
As I hope you agree that complexity leads to very subtle, hard to
reproduce/fix problems... So if using the Global Exception Handlers
reduces the amount of complexity in my code, then I will use them unless
said code is not compatible with the Global Exception Handlers! (such as
asynchronous methods).
The code should never be more complex then it needs to be, but it should
especially not be less complex then it needs to be. Using UE as the primary
mechanism to deal with unanticipated problems make it far more complex (in
my opinion).
(In other words is the class half empty or is it really half full?)
I would argue that the glass is twice as large as it needs to be.
I agree, pushing the recovery off to the program being launched, which can
easily be forgotten, opens the app up to very subtle, hard to
reproduce/fix problems. Where as a Global Exception Handlers helps ensures
that someone is handling the exception, whether its the launchee or the
launcher. I'm saying "helps ensures" as I've seen where exceptions have
been missed by even a Global Exception Handlers, such as asynchronous
methods. In other words if anything I'm suggesting you really should have
both! Especially if you are using a launcher!
You miss my point...there should always be a UE handler somewhere, if only
for the reason of logging the exception, and asking the user to continue or
abort. But that is simply a last-ditch effort! How would the user even know
if it was safe to continue? The user has no context to evaluate the system.
Conversely, how would the system know that it wasn't? What should be done in
a UE, and would that always be the correct decision in all cases?
It appears (based on your earlier reference to UE) you are only referring
strictly to AppDomain.UnhandledException, where as I am referring to all
three Global Exception Handlers; AppDomain.UnhandledException,
HttpApplication.Error and Application.ThreadException. Plus I am
suggesting that you should use the one that is appropriate, I find
AppDomain.UnhandledException not to be very useful in most Windows Forms
or Web Forms applications. Instead I find Application.ThreadException to
be extremely useful in Windows Forms applications, and
HttpApplication.Error & Page.Error (TemplateControl.Error) to be extremely
useful in Web Forms applications.
These are all useful for various purposes.
In other words: Pick the right tool for the right job!
We can agree on this!
BTW: If you re-read my statements I never stated that using Global
Exception Handlers are an absolute, I simply stated that is what I tended
(normally) used! I hope you agree that not using should not be an absolute
either!
I never argued against using them at all. I argued against using them as the
primary mechanism for dealing with exceptions.
Hope this helps,
Dave