Chapter 14. Best Practices for Robust Exception Handling

 

Computers allow you to make more mistakes faster than any other invention in human history with the possible exception of handguns and tequila.

 
 --Mitch Ratcliffe

In the development world, it is nearly impossible to write bug-free software. The best we can do is write stable software that, when a problem occurs, displays enough information to solve it. There is no way to write software that is bug-free, but by employing exception handling, at least we can gracefully handle any anomalous situations that occur.

An exception can be defined as unexpected behavior or an error condition occurring in a software application. The name itself comes from the idea that, although an error condition can occur, the error condition occurs infrequently. The majority of a developer’s time is spent on user input and error handling. Thankfully, there is some functionality in the .NET Class Framework that provides error handling, but there are certain best practices that should be followed in terms of design concerns and performance issues.

Many developers misuse or overuse exception handling, and this chapter is all about best practices for using .NET exception handling. This chapter is not centered on the design aspects of user interface integration, but rather serves to enlighten readers about the proper way to handle errors while aiming for maximum performance and adherence to framework guidelines.

External Data is Evil

Typically, exceptions in an application are thrown because of invalid or nonexistent data. External data can be provided from a database, keyboard input, files, a registry, or a network socket. You can never trust external data because there is no way to be certain that the external data exists or is valid. You may also end up with insufficient privileges to access the external data.

Aside from reading data, most external data sources have write capability, in which case there is also some sort of repository for the data as well. You may end up with insufficient privileges or not enough memory, or the device can suffer from a physical fault as well. It is important to recognize that the best thing to do is build solid code that handles external data errors in a stable and informative manner.

The safest approach when dealing with external data is to validate the data before doing anything else with it.

Creating Custom Exceptions

There is a common misconception among developers regarding the use of System.Exception and System.ApplicationException. These exception types are used throughout the .NET Class Framework and form the base of many derived types, but you should never throw them explicitly. The truth is, these types are much too broad and generic to be thrown; you should instead be creating and throwing custom exceptions if a suitable exception type does not yet exist.

Another fallacy is that custom exceptions should be derived from System.ApplicationException. This used to be the correct approach but has now been identified by Microsoft as incorrect. You should instead be deriving all custom exceptions from System.Exception. One of the main reasons for this change is an assortment of issues when using third-party libraries in your code. You may have a function that calls a third-party method, which in turn throws a System.ApplicationException, and you may also throw that same exception later on in your code. When the exception leaves the method and arrives at the exception handler, who threw it? The best approach is to create exception class hierarchies that are as wide and shallow as possible, the same approach typically used with the structuring of class hierarchies.

All custom exceptions should also support the three default constructors to promote consistency between your custom exceptions and the built-in framework exceptions.

These constructors are:

const string defaultMessage = "Your default message";

public YourException()
    : base(defaultMessage);

public YourException(string message)
    : base(String.Format("{0} - {1}", defaultMessage, message));

public YourException(string message, Exception inner)
    : base(String.Format("{0} - {1}", defaultMessage, message), inner);

There are some rules that must be followed in regards to the message property. Do not store exception information in the message property. Instead, create separate properties in the exception class to hold the data. Storing information in the message property also means that users will have to perform string parsing to retrieve the data, which is an obvious hassle. Also consider the problems that localization would present if you were attempting to parse different pieces of information from the exception message when the formatting of the string was based on the current culture locale.

Lastly, be sure to mark your exceptions with the [Serializable] attribute. You never know when your methods will be called from Remoting or a Web Service.

Here is a complete example of a custom exception in its simplest form:

[Serializable]
public class NexusException : System.Exception
{
    const string defaultMessage = "A runtime error occurred within" +
                                  "Nexus World Builder";

    public NexusException()
        : base(defaultMessage)
    {
    }

    public NexusException(string message)
        : base(String.Format("{0} - {1}", defaultMessage, message))
    {
    }
public NexusException(string message, Exception inner)
        : base(String.Format("{0} - {1}", defaultMessage, message), inner)
    {
    }
}

Throwing Exceptions

A trait of exceptions is that they cannot be ignored, so it is a good idea to use exceptions in place of return values when a particular operation must be successful to proceed. It is also wise to rely more on throwing an exception than using Debug.Assert. Keep in mind that assertions are removed from release code, so errors will be much harder to track down in a production environment.

The stack trace is a critical piece of information to have when an exception is thrown, and extra care must be taken when re-throwing exceptions to ensure that the stack trace is preserved. Many times you need to catch a particular exception, perform cleanup logic such as a transaction rollback, and then re-throw the exception so that another exception handler can process it.

Consider the following code:

try
{
    // Code that throws an exception
}
catch (Exception exception)
{
    // Code that performs cleanup  rollback
    throw exception;
}

In this example, the exception is caught and thrown back to the next exception handler, but using a new exception object without the stack trace. The proper way to re-throw the exception while preserving the stack trace information is shown in the following code:

try
{
    // Code that throws an exception
}
catch (Exception exception)
{

    // Code that performs cleanup  rollback
    throw;
}

Just calling throw alone will re-throw the exact same exception object that arrived at the catch statement in the first place.

Also, be sure to add a semantic value if you re-throw an exception under a different type. Sometimes you may wish to take a few specific exceptions and re-throw them under a more generalized type, but it is advisable to at least attach the old exception as the inner exception property when re-throwing it. That way, the original exception is readily available if there is a need for re-specialization.

Lastly, it is bad design to use exceptions as a means of returning information from a method. One reason is that exception handling is fairly slow, so overuse and misuse of exception handling can introduce many performance bottlenecks into your code.

Structured Exception Handlers

There are some rules that should be followed when building your exception handlers and deciding what exception types to catch. Never catch a base-type exception when you are always expecting a more specialized one. For example, do not catch System.Exception when the only exception that will ever be thrown is a System.ArgumentNullException. Generic handlers create many problems, and should be avoided whenever possible. A general rule of thumb is that System.Exception should only ever be caught once per thread.

If you want more reasoning behind this rule, consider the following example.

Imagine that you build a library that offers fairly basic functionality, and it is used by a Windows Forms application. Now, normally a method in this library will throw System.ArgumentNullException when a specified parameter is null. If the Windows Forms application has a structured exception handler that catches System.Exception, error handling will work as expected in a perfect situation. In the event that the library assembly cannot be referenced by the Windows Forms application because it is missing, the Common Language Runtime will throw a System.IO.FileNotFoundException, indicating that the library assembly could not be found. When this exception occurs, the Windows Forms application believes that a null parameter was specified, when in reality the entire library could not be found. However, if the Windows Forms application did not catch generic exceptions, this problem would be avoided.

Lastly, one of the absolute worst things you can do is catch an exception and do nothing with it. Catching an exception with an empty code block is commonly referred to as exception swallowing; do not do this! If you do not wish to handle a certain type of exception, don’t write an exception handler for it.

Logging Exception Information

At first glance, it may look correct to spit out the contents of Exception.Message to whatever logging medium you are using, but such an assumption would be incorrect. The Exception.Message property only contains the high-level message, which could be as informative as Object reference not set to an instance of an object. A better approach instead is to log Exception.ToString(), which will result in the logging of the message, the inner exception, and the stack trace of the exception—information that is much more useful when it comes time to debug a problem.

Mechanisms for Cleanup

There are a number of classes in the .NET class framework that require cleanup after their role has been fulfilled, and certain classes (like file system access) can lead to locking or other problems when not properly disposed.

Consider the following code:

public void DoSomething(string fileName)
{
    StreamReader reader = new StreamReader(string fileName);
    ProcessStream(reader);
    reader.Close();
}

All is well if no errors occur, but consider the situation where ProcessStream throws an exception. The close method for the StreamReader would never be called, and the resource would remain active.

One solution to this problem is to introduce an exception handler that closes the StreamReader when an error occurs, and then re-throws the exception. Consider the following code:

public void DoSomething(string fileName)
{
    StreamReader reader = null;
    try
    {

         reader = new StreamReader(string fileName);
         ProcessStream(reader);
         reader.Close();
    }
    catch (Exception exception)
    {
        If (reader != null)
        {
            reader.Close();
        }
        throw;
    }
}

This solution will ensure that the reader is always closed, but the design of it is somewhat messy; code is duplicated and it is harder to read.

Structured exception handling in .NET also offers the finally block, which executes when the runtime leaves the exception handler, regardless of whether or not the try or the catch fired.

The following code shows how this would look:

public void DoSomething(string fileName)
{
    StreamReader = null;
    try
    {
        reader = new StreamReader(string fileName);
        ProcessStream(reader);
    }
    finally
    {
        if (reader != null)
        {
            reader.Close();
        }
    }
}

To present an even better approach than the finally mechanism, C# has the wonderful keyword using that implicitly implements the disposable design pattern and ensures that the resource it is attached to cleans up, even in the event that an exception occurs. The using keyword only works on classes that implement the IDisposable interface, but that also means you can create custom classes that require a cleanup process and use this keyword on them.

The following code shows how the using keyword works:

public void DoSomething(string fileName)
{
    using (StreamReader reader = new StreamReader(string fileName))
    {
        ProcessStream(reader);
    }
}

This solution is much more elegant than an ugly exception handler, and still ensures that the resource is released when it is no longer needed.

Unhandled and Thread Exception Events

There are a few issues when using either the AppDomain.UnhandledException event or the Application.ThreadException event. The notification fires so late that by the time you receive the exception notification, your application will be unable to respond to it. Additionally, you will not receive any notifications if your exception was thrown from the main thread or unmanaged code.

It is also very difficult to write a generic exception handler for the entire application that is robust and flexible enough to accommodate and correctly handle every erroneous situation. Because a generic handler would not have access to the local variables present when the exception was thrown, the need to rely on global variables and singletons will be increased, which is something that should be ultimately avoided.

With such faults, you are probably wondering why these events should be used in the first place. Consider them as “safety nets” for the situations where an exception slips through and would be normally handled by the default exception handler provided by the Common Language Runtime.

Conclusion

Structured exception handling is and will remain an integral part of any software project. This chapter covered some best practices for using .NET exception handling, and it is highly advisable that you adopt these new techniques and approaches into your development projects. Doing so will improve both the design and performance of your code and will increase the overall maintainability of your software.

While you could build your own exception handling manager that offers many fancy features that other developers would be envious of, it is important to remember that software development is about building software that meets business needs and doing so in a timely manner. Reinventing the wheel is generally ridiculed, so there are a couple of components available from Microsoft that can be used when there is a need for advanced exception handling support.

The first component is the Exception Handling Application Block that offers the ability to create a consistent strategy for processing exceptions on all architectural layers of an application. This component is not limited to service boundaries, which is an important feature for distributed architectures. Several tools are included with the installation that help you create and configure exception policies for your application. The Exception Handling Application Block can be downloaded from MSDN.

The other component is the Logging and Instrumentation Application Block, which allows for .NET applications to be built for manageability in a production environment. Applications can leverage existing logging, tracing, and eventing mechanisms built into Windows, and can issue a variety of warnings, errors, audits, diagnostic events, and business-specific events. This component also provides statistics like average execution time for a process or service. This component can also be downloaded from MSDN.

 

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.152.198