Although few, if any, software developers really believe that the code that they write is bug free, few spend any time looking at what their code will do when it does encounter a bug or an unexpected condition. Some developers blindly let the language use its own default error handlers, which is generally referred to as letting the application puke. The other common way to handle an exception is to report little or no information and blindly continue on. However, neither approach is appropriate nor helpful.
Most of the money spent on software is not during its development. It is spent during the maintenance phase of a project when responding to slowly changing environmental conditions can cause a wide variety of subtle problems that can be difficult to detect without the proper tools. When handled correctly, exceptions can be the key to finding and resolving problems.
If your application is designed and written correctly an exception means that a problem has occurred which you did not anticipate. This will generally result in some problem, which the system administrator will be looking to solve. If you choose to eat the exception and never log it or raise it to the user you will be destroying data, which is often essential for the administrator's ability to resolve the problem. Even the most basic exception contains information in it that can be useful to the troubleshooting process. The call stack when the exception was thrown and the type of exception can be invaluable at helping to determine what happened.
By focusing on how problems with the system are identified and isolated will help you to understand how critical proper exception handling is. If you doubt this, wander down to the infrastructure group and ask them which applications they like to support the most and which ones they like to support the least. The answer is often found in how easy or hard it is to identify and resolve problems.
Despite the clear value of any exception, what would be even more valuable is to know all the details about the moment the exception occurred including what data was in use at the time the error occurred, or in other words, logging what data conditions caused the exception to occur in addition to the call log. For a divide by zero type exception it may be obvious what caused the exception. However, in most other situations it will not be obvious at all.
The solution is to wrap the exception that you get into another exception that contains all the information from the original exception but also includes other pertinent data, such as all of the fields/properties from the object. This not only allows you to see where the exception occurred and the kind of the exception but it also allows you to get a glimpse of what was going on at the time.
In a language like C# it generally makes sense to do a try catch block in each function and to serialize the object (this) into a string, use this as the text for the outer exception, and finally include the inner exception which caused the catch block to be invoked. This makes sense because most of the data that a method call works on is wrapped up in the object. This means that within a few lines of code you can dramatically improve the meaningfulness of an exception. Take a look at the following code snippet:
Obviously, the object in question will need to have a Serialize method that returns a string representation of the object. However, in one line of code you have just taken an inner exception and wrapped it up with the data context that it occurred in. A solid serialization routine can even walk down through the object and its component objects so that a very detailed representation of the data objects involved are included.
If you have a method that does take parameters or you need more data you can, of course, add additional information to the outer exception. This involves adding the parameters of the call to the outer exception as well. In C# this can be done by picking up the parameters from the System.Diagnostics.Stacktrace class. Of course, it can be done by hand as well.
You may have noticed in the preceding example that the exception is re thrown to the calling method or function. This is done so that it too can wrap up any information that it can offer into the exception. The end result is that the exception is caught and re-thrown with additional data several times before finally reaching the highest level.
One of the common concerns with this approach is the performance required to generate so many exceptions and raise them to the controlling process. Since exceptions should be used only in exceptional cases where no other normal processing is possible, the additional processing time to handle the same exception a dozen times or so is minimal. The offset to that minimal processing overhead is a greatly improved ability to diagnose problems.
Another common objection to this process is that the exception that is generated will become quite large quite quickly. This is true, however, the number of times that these exceptions are being thrown is minimal thus the memory impact on nested exceptions will be minimal as well.
Of course, the last step is actually handling the fully wrapped and complete exception at the top level. There are really only a few ways to respond to the exception. The first way is to ignore the exception. If you have spent time wrapping up the exception to make sure that you have everything that you need you are not likely to take this approach.
The second approach is to raise the exception to the user. Because of the sheer size of the exception you may not want to try this approach since you could very easily overwhelm a user. In addition, you may find that there is some data in the objects that could be serialized that you may not want to dump back to the user. You might have internal Ids, which they should never see, or private system administrator comments.
The third approach is to log the error and communicate to the user that the error has been logged. This option, by far the best of the three, records the full text of the exception into a database or other storage mechanism, sends the client that an error has occurred and has been logged, and even notifies the system administrator.
Handling exceptions with care can create systems, which are easy to support without a tremendous amount of work put into creating logging.