posted on Wednesday, August 17, 2005 11:16 AM by anoras

Some tips on Exception Handling

When I mentor developers, exception handling is a common topic in the Q&A-sessions. Developers with a Visual Basic 6.0 background remember “on error”-phrases, while Java-developers are marked by the troubles of checked exceptions. There are two common exception handling strategies used by many novice .NET-developers; either they catch System.Exception everywhere or they completely ignore exceptions.

Luckily, people are asking questions and they are willing to learn more about what to do with exceptions. Questions like “Should I wrap every code-block with a try/catch?”, “What should I do when an exception occurs?” and “How can I log an exception?” are all common. 

Catching exceptions
The “on error resume next”-construct in old-school Basic was a really bad idea, so there is no need to recreate the feature using VB.NET or C#. Nobody really does this intentionally, but in a sense, this is what the code in Example 1 does.

try

{

    this.OperationWillFail();

}

catch {}

Example 1 – “on error resume next” in C#

The example above is an obvious source of unexpected behavior, and experienced developers seldom write this kind of code. However; some patterns used by intermediate developers are related to the previous example. For instance, it is not uncommon to have a method return a Boolean indicating success or failure, and returning false if an exception occurs within the method. In some places, such as the Int32.TryParse method in .NET 2.0, this behavior is appropriate because of the semantics of the method signature; TryParse has an out parameter. As a rule-of-thumb you should avoid out parameters, and hence TryParse is an exception to general exception handling practices. There are multiple reasons why replacing exceptions with success-indicators is a bad practice, but the most important one is that you remove valuable information about the cause of the exception making the application difficult to debug.
Instead of being embarrassed about exceptions occurring in your code, let the exceptions bubble up through the call-hierarchy to a place where you have enough contextual information to handle the exception. Consider the following scenario; you have written a piece of code to import a CSV-file from a legacy system. The legacy system is unstable, so sometimes the data has to be retrieved from a backup system. Depending on the availability of the legacy system, the data the files are stored in different directories. You have written code similar to Example 2. This code works because you know that if the “LegacySystem” directory doesn’t exist, you’ll have to retrieve the data from the backup system. This code would fail miserably if the System.IO team at Microsoft pretended that the “LegacySystem” directory exists when it doesn’t.

try

{

    File.OpenText(@"C:\LegacySystem\Data.csv");              

}

catch (System.IO.DirectoryNotFoundException)

{

 

    File.OpenText(@"C:\BackUpSystem\Data.csv");                              

 

}

Example 2 – Using contextual information to handle exceptions.
 

Exceptions and Performance
There are two exception related performance issues in the previous section; with a method signature akin to the TryParse method it is alright to attempt something as long as you catch any exception that can occur and in Example 2 it is alright to use exceptions for control-flow. Observing it from the outside, the .NET 2.0 Int32.TryParse method would be implemented as in Example 3, but if you take a peek under the covers, you’ll discover that this the method guards itself from FormatExceptions being thrown whatsoever.

public bool TryParse(string s, out int i)

{

    try

    {

        int i=int.Parse(s);

        return true;

    }

    catch (FormatException)

    {

        return false;

    }

}

Example 3 – A naïve TryParse implementation

This is essential, because using exceptions for control-flow has huge performance penalties. Avoiding flow-control through exceptions has numerous benefits, apart from increased performance; you’re code turns out to be more readable. The code in Example 4 does the same as Example 2, only faster and the behavior comes across almost as if it was written in plain English.

if (Directory.Exists(@"C:\ LegacySystem \"))

{

    File.OpenText(@"C:\ LegacySystem \Data.csv");              

}

else

{

    File.OpenText(@"C:\ BackUpSystem \Data.csv");                              

}

Example 4 – Being conscious about control-flow                                              

Throwing Exceptions
Most of the time, you’ll be playing the role of the catcher, but sometimes you’ll have to be the one throwing exceptions. You should use the advice in the previous sections to eliminate the need for an exception to ensure that your method succeeds performing it’s propose and can return safely to the caller. However, if your method fails always throw an exception and never resort to returning error codes. Because of the severe nature of an exception, an exception will get noticed.
Even though it isn’t; the System.Exception class should be regarded as an abstract class, therefore you should never throw this exception. Instead you should try to find a suitable exception from the .NET framework’s vast set of predefined standardized exceptions. These exceptions are well-known to all developers, so when such an exception is thrown, developers will have the necessary knowledge about the circumstances that caused the exception to resolve the problem if they the contextual information needed. 
For instance, you should always throw an ArgumentException, or an exception derived from this class, if an invalid parameter is passed to your method.
If you except users of your class library to take programmatic actions based on exceptions, there are two scenarios where you should define new exceptions; if none of the standard exceptions in the framework are applicable or if you need to add additional details to an existing exception. If you have to define a new exception you should derive your exception from System.ApplicationException. The Design Guidelines for Class Library Developers also mentions System.SystemException where System.ApplicationException is discussed, this class serves as a base class for the exceptions in the System namespace and unless you’re on the Microsoft BCL team, you won’t be adding new classes to that namespace. If you’re defining multiple exceptions in a single namespace, you should consider following the same pattern as Microsoft does with System.SystemException; that is define a base exception for the entire namespace and derive from this exception to group the exceptions within the namespace.

If you need to add additional information to an existing exception, you should derive from the exception in question in lieu of embedding the information in the message.  For instance; the .NET framework’s System.DuplicateWaitObjectException is derived from System.ArgumentException. This makes it a breeze to detect that an object appears more than once within an array of synchronization objects. If the BCL-team had stuck to a vanilla System.ArgumentException, you would have to parse the exception message to detect this. This would be impractical, result in verbose and redundant code and it wouldn’t have optimal performance. 

Back to Catching Exceptions…
As we learned earlier, you shouldn’t catch exceptions you can’t handle. This doesn’t imply that it is OK to ignore exceptions and hope that they can be handled higher in the call-stack. You should catch every exception you can handle, but don’t get eager and try to handle every possible exception a method might throw as this will make your code brittle to change and virtually impossible to test. Even though you shouldn’t throw a pure System.Exception, you can catch it. This is a much better approach than trying to catch tons of different exceptions, just remember that you should let exceptions you can’t handle pass. 

Re-throwing Exceptions
You should avoid re-throwing exceptions unless you’re exchanging the exception with a new exception or adding additional information to an exception. When you exchange an exception you should chain the original exception to the new exception as an inner exception as shown in Example 5. Exchanging exceptions for other exceptions is a useful technique for making the cause of exceptions clearer to the users of your library at public boundaries. 

try

{

    // Open the file... 

}

catch (Exception e)

{

    throw new IOException("Could not open file.",e);

}       

Example 5 – Chaining exceptions

If you need to just re-throw an exception, you should only use a throw statement without an argument as this compiles to the IL instruction rethrow instruction. This instruction is only permitted within catch-block and it is used to re-throw the current exception without altering the stack trace. If you use throw with the caught exception as an argument, the stack trace is rooted to your method as opposed to the exception’s real origin. 

Some Final Words on Finally
While catching and re-throwing exceptions is far better than just catching exceptions from a debugging point-of-view, you should try to use the try/finally pattern as often as possible. The code in a finally block is always executed, hence the finally block is a perfect place to cleanup when an exception occurs while still allowing the exception to be raised. If you make a habit of using this pattern, you won’t accidentally forget to re-throw an exception in the catch block either.

Conclusion
Exception handling is a vast topic and I’ve merely scratched the surface here. If you wish to dive deeper into best-practices the Error Raising and Handling Guidelines from Design Guidelines for Class Library Developers is a good place to start. One important lesson to learn is that you shouldn’t “eat” exceptions by catching them without taking any action. Another is that exceptions should truly be exceptional; therefore you should never use exceptions for flow-control or any other common tasks.

 

Comments