My musings about .NET and what not

Defensive Programming, or Why Exception Handling Is Like Car Insurance

What can driving a car teach us about exception handling? Apparently, quite a lot.


If you drive a car, you probably have automobile insurance. But even though you may be covered by insurance, an accident can still be pretty expensive, financially and otherwise. You would probably agree that no matter how much insurance you may have in place, it’s smart to be a “defensive driver” and avoid getting into an accident in the first place.

Exceptions are like accidents, and exception handling is like car insurance. While it’s vitally important to have, and though it may get you “back on the road,” exception handling is really no substitute for good defensive programming. Exceptions are “expensive” in terms of the resources and additional overhead they incur. Because of this, you should anticipate when, where, and how exceptions could be thrown, and do your best to “steer” your code around them.

Always Validate Input

Input validation is like buckling your seat belt - it's the very first thing you should think about before working with any kind of user input. Validation is the most important measure you can take to ensure that your application doesn’t have to deal with bad input in the first place.

Consider the following LogEntry method, which takes an entry supplied by a user and writes that entry into a text file:

protected void LogEntry(object sender, EventArgs e)
{
   string path = GetPathToLog();
   StreamWriter writer = File.AppendText(path);
   writer.WriteLine(DateTime.Now.ToString(CultureInfo.InstalledUICulture));
   writer.WriteLine("Entry: {0}", txtLogEntry.Text);
   writer.WriteLine("--------------------");
   writer.Dispose();
}

This method has a call to File.AppendText (which takes as an argument a string indicating the physical path to the file you want to append to), as well as several calls to the StreamWriter.WriteLine method. Now, stop for a moment and think about all the things that could go wrong here.

  • path could be an empty string.
  • path could be null.
  • path could be too long.
  • path could contain invalid characters.
  • path could be in an invalid format.
  • path could point to a file outside the web site.
  • path could point to a valid directory, but not a specific file.
  • The directory specified in path may not exist.
  • The file specified in path may not exist.
  • The file may be read-only.
  • The ASP.NET account may not have sufficient permissions on the directory.
  • The file may be locked by a previous process.
  • There could be a disk error while opening the file or writing to the disk.
  • Something could go wrong outside of the file I/O system — the computer’s memory could suddenly become corrupted, for example.

If any of the above scenarios actually does take place, a runtime error will occur, and the CLR will throw an exception.

Now assume that the GetPathToLog method (line 3 in the above code) relies on the user to enter a path into a textbox on a web page. By adding some validation to that textbox, you can eliminate some of the exception cases you have to deal with, as shown below:



You can see that by using a RequiredFieldValidator, you ensure that path can never be null or empty. By using a  RegularExpressionValidator, you ensure that the path cannot contain invalid characters or be in an invalid format. And by setting the MaxLength property on the textbox, you ensure that the path cannot be too long. Because these validations have eliminated the possibility of some exceptions being thrown, you don’t need to worry about handling them.

Validate or restrict user input to reduce the likelihood of exceptions being thrown.

Check for Error Conditions

Much like automobile insurance, exception handling only kicks in after something bad has already occurred. However, you often have the option of programmatically determining if an error condition could potentially occur before it actually does.

The option you choose — exception handling or programmatically checking for error conditions — depends on whether the condition is truly exceptional (a state that occurs infrequently or unexpectedly) or non-exceptional (a normal and predictable condition).

  • If the condition can be considered exceptional, then using exception handling is more efficient because fewer lines of code are executed under normal conditions.
  • If the condition is non-exceptional, then programmatic checking is more efficient because fewer exceptions will be thrown.

This is best illustrated by an example. Let’s say that you have a blog application in which you allow users to rate blog posts from 1 to 5. Let’s also say that you want to display the average rating for each post. If you store the total rating a post has received, along with the number of times it has been rated, you can calculate the average rating as shown below:

public double AverageRating
{
   get { return (double) TotalRating / NumOfVotes; }
}

This works great — as long as the article has been rated by at least one person. But what if the article has never been rated? In the code above, if NumOfVotes is 0, the getter will fail with a DivideByZeroException.

Now, let’s see what we can do to fix this:

public double AverageRating
{
   get
   {
      try
      {
         return (double) TotalRating / NumOfVotes;
      }
      catch (DivideByZeroException)
      {
         return 0.0;
      }
   }
}

Well, this takes care of the case in which the article has never been rated, and that’s what we wanted to do. However, this solution is less than ideal because it allows an exception to be thrown in what really can’t be considered an exceptional circumstance. That a post has not yet been rated is a totally normal and predictable state of affairs. Relying on an exception to handle normal cases like this is like parking your car on the train tracks — it’s only a matter of time before the inevitable happens.

Now, consider this option instead:

public double AverageRating
{
   get
   {
      if (NumOfVotes == 0)
      {
         return 0.0;
      }
      else
      {
         return (double) TotalRating / NumOfVotes;
      }
   }
}

The code above presents a much better solution, because it takes unrated posts into account without relying on an exception to do so.

The example I’ve just presented shows how to avoid having to handle a DivideByZeroException by testing a divisor’s value before performing division arithmetic. There are several other defensive programming strategies you can use to avoid many commonly thrown CLR exceptions.

Defensive Programming Strategies

  • Check for Nulls — Trying to use a null object in code will result in a NullReferenceException. If there is any possibility an object could be null, check for that before calling its methods or accessing its properties.
     
  • Check Object State — The current state of an object may prevent certain methods from being called on it. For example, attempting to call the Close method on a connection that is already closed will throw an InvalidOperationException. Check the state of an object before performing operations that require a specific state.
     
  • Check Your Arguments — Many methods and constructors require arguments to be non-null, in a certain format, or within a range of acceptable values. For example, attempting to create a DateTime of February 30, 2009 will result in an ArgumentOutOfRangeException, because February doesn’t have 30 days. Make sure that the arguments you pass conform to the expectations of the method you are invoking.
     
  • Be Careful When Working with Arrays — Remember that the elements of an array are of a certain type and that arrays are always of a fixed length. Attempting to store an element of the wrong type in an array throws an ArrayTypeMismatchException. Attempting to access an element with an index less than zero or greater than the size of the array throws an IndexOutOfRangeException. Try to determine if these conditions could occur, and write your code accordingly.
     
  • Establish Bounds for Iterations and Recursions — While recursive methods (methods that call themselves during execution) are sometimes very handy, you need to guard against the possibility of infinite recursion — lest your execution fall into an endless loop, resulting in a nasty StackOverFlowException. The StackOverFlowException is unique in the .NET Framework because, unlike most other exceptions, it cannot be caught in a try-catch construct. Therefore, this is one of those rare situations in which programmatic handling is your only choice. Make sure that you don’t accidentally code an endless loop by failing to define a condition that, when met, will cause the recursion to end.
     
  • Use Safe Casting — If you attempt to cast an object to a type with which it is not compatible, an InvalidCastException will result. This usually happens when the type of the object you are casting is not known at design time. In these cases, you can use the as or is operators to prevent the exception. The as operator first checks if the cast can be performed successfully, and if so, returns the result of the cast; otherwise, null is returned. The is operator only returns true if the cast would succeed, and false otherwise, without actually casting the object.
     
  • Use Safe Parsing — In .NET, value types expose Parse and TryParse methods, both of which convert a string representation of a value to an actual value of the type. The difference between the two is that Parse throws a FormatException if the string cannot be converted, while TryParse only returns a Boolean value — true if the conversion succeeds, and false if it does not — while returning the actual parsed value as an out parameter. When performing parsing operations you suspect may fail, you can use TryParse to prevent an exception from being thrown.

Keep in mind that programmatic checking doesn’t make sense in all cases. For example, you might think that checking the value of File.Exists before calling File.Open is a good idea, but a race condition exists in which the file may be deleted in the split second that elapses between the two method calls. Therefore, in this case, you should handle the FileNotFoundException, by prompting the user for a different filename or by creating the file.

Programmatically check for ordinary cases that might cause an error condition. Allow exceptions to be thrown only in exceptional cases, or when a race condition makes checking impractical.

This is by no means an exhaustive list of defensive programming techniques, but you get the idea.

Summary

  1. Validate or restrict user input to reduce the likelihood of exceptions being thrown. 
  2. Devise defensive programming strategies to prevent your code from causing exceptions to be thrown when appropriate. 
  3. In cases where truly exceptional circumstances may occur, or where programmatic error checking is impractical, be prepared to handle the resulting exception as needed.

Following these few simple rules probably won't do much to keep your car insurance rates down, but they'll definitely keep your code running a whole lot smoother!

Subscribe to this blog for more cool content like this!

kick it on DotNetKicks.com

shout it on DotNetShoutOut.com

vote it on WebDevVote.com

Bookmark / Share

    » Similar Posts

    1. Integrating Exception Handling Into the Development Cycle
    2. Should We Return Null From Our Methods?
    3. If At First You Don’t Succeed - Retrying Mail Operations in .NET

    » Trackbacks & Pingbacks

    1. You've been kicked (a good thing) - Trackback from DotNetKicks.com

      Defensive Programming, or Why Exception Handling Is Like Car Insurance — July 16, 2009 10:33 AM
    2. Thank you for submitting this cool story - Trackback from DotNetShoutout

      Defensive Programming, or Why Exception Handling Is Like Car Insurance — July 16, 2009 10:34 AM
    3. You are voted (great) - Trackback from WebDevVote.com

      Defensive Programming, or Why Exception Handling Is Like Car Insurance — July 16, 2009 10:36 AM
    4. Pingback from Dew Drop – July 17, 2009 | Alvin Ashcraft's Morning Dew

      Dew Drop – July 17, 2009 | Alvin Ashcraft's Morning Dew — July 17, 2009 7:19 AM
    5. Pingback from Exceptions: The Airbags of Code ??? Global Nerdy

      Exceptions: The Airbags of Code ??? Global Nerdy — August 10, 2009 8:30 AM
    6. The time to start thinking about exception handling is right after you click File > New Project. Exception handling shouldn't be something you "tack-on" to an application after the fact. Here, I discuss a practical approach to incorporating

    Trackback link for this post:
    http://leedumond.com/trackback.ashx?id=69

    » Comments

    1. Syed Tayyab Ali avatar

      It is detail explanation on exception handling case.

      Syed Tayyab Ali — July 16, 2009 3:38 PM
    2. JeroenH avatar

      Although I mostly agree, I would like to add that code can also become more difficult to read when each and every method has to perform all these checks. At the main entry boundaries of your application, you should obviously validate all input, but all too often I see cases where each and every (even private) method repeats the same verifications (typically null checks) over and over again. This adds a lot of unnecessary clutter and makes the code much less readable.

      JeroenH — July 18, 2009 12:59 PM
    3. Syed Tayyab Ali avatar

      Yes, double or multple time of check on same input is really bad thing. It is cause performance.

      Syed Tayyab Ali — July 20, 2009 1:53 PM
    4. Janusz Leidgens avatar

      Nice article. Sounds really good to avoid all this exceptions by checking before doing an action...

      And the Divide by Zero example is the simple case of error handling.

      But there is one thing I miss in this article. What should I do in a case I don't know what to do with the wrong state. If I'm a small function that gets an object passed and then it is null. Nice i know it is null. But what now? return null? Do nothing? Write a log message? Exit the program?

      I think the good way to handle this for the function is to throw an exception. In most of the times it can be an unchecked Exception but something has to happen and the calling function needs to know what happened and why it happened. So yeah you should avoid running into exceptions stupidly but in the cases you can't handle the error at the moment one of your nice defensive programming strategies found it before it happened you need the exceptions for propagating the problem back to the program. On a higher abstraction level maybe a invalid argument or invalid state exceptions.

      I think without explaining how to handle this cases the article is not complete at all.

      Janusz Leidgens — August 4, 2009 6:42 PM
    5. Colin avatar

      What you don't do is explain what do to if the seat-belt checks fail. The answer is that it depends on what tier you are at. At the UI tier you want to give useful message. At the BO/DA tier you should throw an exception. When to throw and when to handle are topics that should be dealt with separately.

      Colin — August 5, 2009 10:17 AM
    6. Lee Dumond avatar

      @Colin - This post has nothing at all to do with thowing exceptions. That's really a framework/API design issue, which I do plan to address in an upcoming post.

      This post merely deals with using programmatic checks (when practical) to avoid having to handle the most common CLR exceptions.

      Lee Dumond — August 5, 2009 10:39 AM
    7. Colin avatar

      I guess my point is that I'm just finishing off what you started by saying what you do when you drive defensively and someone comes right at you with a bad turn - you beep the horn, i.e. throw an exception, so the consumer of the code gets an exception either way. The benefit of a defensive approach is that it's an exception that one can recover from (a beep of the horn and some mad braking vs. a full air bag reset and aborting of the journey).

      Colin — August 5, 2009 2:54 PM
    8. Jay Cincotta avatar

      Excellent article on defensive programming. I also love the analogy with car insurance. In fact, we use the same analogy in describing how our product, Gibraltar, silently captures and reports unhandled exceptions (along with a ton of other useful support information).

      www.gibraltarsoftware.com/.../Default.aspx

      Jay Cincotta — August 26, 2009 8:18 AM

    » Leave a Comment