Exception handling provides much-needed structure to the error-handling mechanisms that preceded it. However, it can still make for some unwieldy results if used haphazardly. The following guidelines offer some best practices for exception handling.
Catch only the exceptions that you can handle.
Generally it is possible to handle some types of exceptions but not others. For example, opening a file for exclusive read-write access may throw a System.IO.IOException because the file is already in use. In catching this type of exception, the code can report to the user that the file is in use and allow the user the option of canceling the operation or retrying it. Only exceptions for which there is a known action should be caught. Other exception types should be left for callers higher in the stack.
Don't hide (bury) exceptions you don't fully handle.
New programmers are often tempted to catch all exceptions and then continue executing instead of reporting an unhandled exception to the user. However, this may result in a critical system problem going undetected. Unless code takes explicit action to handle an exception or explicitly determines certain exceptions to be innocuous, catch blocks should rethrow exceptions instead of catching them and hiding them from the caller. Predominantly, catch(System.Exception ) and general catch blocks should occur higher in the call stack, unless the block ends by rethrowing the exception.
Use System.Exception and general catch blocks rarely.
Virtually all exceptions derive from System.Exception. However, the best way to handle some System.Exceptions is to allow them to go unhandled or to gracefully shut down the application sooner rather than later. Exceptions like System.OutOfMemoryException are nonrecoverable, for example, and the best course of action is to shut down the application. Such catch blocks should appear only to run cleanup or emergency code (such as saving any volatile data) before shutting down the application or rethrowing the exception with throw;.
Avoid exception reporting or logging lower in the call stack.
Often, programmers are tempted to log exceptions or report exceptions to the user at the soonest possible location in the call stack. However, these locations are seldom able to handle the exception fully and they resort to rethrowing the exception. Such catch blocks should not log the exception or report it to a user while in the bowels of the call stack. If the exception is logged and rethrown, the callers higher in the call stack may do the same, resulting in duplicate log entries of the exception. Worse, displaying the exception to the user may not be appropriate for the type of application. (Using System.Console.WriteLine() in a Windows application will never be seen by the user, for example, and displaying a dialog in an unattended command-line process may go unnoticed and freeze the application.) Logging- and exception-related user interfaces should be reserved for high up in the call stack.
Use throw; rather than throw <exception object> inside a catch block.
It is possible to rethrow an exception inside a catch block. For example, the implementation of catch(ArgumentNullException exception) could include a call to throw exception. However, rethrowing the exception like this will reset the stack trace to the location of the rethrown call, instead of reusing the original throw point location. Therefore, unless you are rethrowing with a different exception type or intentionally hiding the original call stack, use throw; to allow the same exception to propagate up the call stack.
Use caution when rethrowing different exceptions.
From inside a catch block, rethrowing a different exception will not only reset the throw point, it will also hide the original exception. To preserve the original exception set the new exception's InnerException property, generally assignable via the constructor. Rethrowing a different exception should be reserved for situations where
Changing the exception type clarifies the problem.
For example, in a call to Logon(User user), rethrowing a different exception type is perhaps more appropriate than propagating System.IO.IOException when the file with the user list is inaccessible.
Private data is part of the original exception.
In the preceding scenario, if the file path is included in the original System.IO.IOException, thereby exposing private security information about the system, the exception should be wrapped. This assumes, of course, that InnerException is not set with the original exception.
The exception type is too specific for the caller to handle appropriately.
For example, instead of throwing an exception specific to a particular database system, a more generic exception is used so that database-specific code higher in the call stack can be avoided.