Assertions and Exceptions

 < Day Day Up > 



Assertions and exceptions have similar purposes; they both notify you that something is not right in your application. In general, you’ll want to reserve assertions for design-time debugging issues and use exceptions for runtime issues that might have an impact on users. Nevertheless, you should know how to use both tools to catch problems early.

Assertions

When you use an assertion, you assert that a particular Boolean condition is true in your code and that you want the Microsoft .NET runtime to tell you if you’re wrong about that. For example, in the DownloadEngine component of Download Tracker, it doesn’t make sense to try to download a file from an empty URL. Ideally, I’ll never write code that attempts this; there should be validation of user input to prevent this situation. But if it does happen, through some mistake on my own part (or as a result of some undue sneakiness on the part of the user), I’d rather know immediately.

To use assertions in .NET, you need to set a reference to the System.Diagnostics namespace. Then you can use the Debug.Assert or the Trace.Assert method. For example, here’s a snippet of code to verify the presence of some data in the SourceUrl property:

 // This method should never be called without  // a Source URL  Debug.Assert(((d.SourceUrl != string.Empty) &&      (d.SourceUrl != null)),      "Empty SourceUrl",      "Can't download a file unless a Source URL is supplied"); 

The first argument to the Assert method is a Boolean expression that the Common Language Runtime (CLR) will evaluate at runtime. If it evaluates to true, then nothing happens, and the code keeps running with the next line in the application.

However, if the Boolean expression evaluates to false, the CLR halts execution and uses the other two arguments to construct a message to the developer. I can test this with the aid of a quick little harness application. Figure 4.1 shows the result of calling into this method with an empty SourceUrl property.

click to expand
Figure 4.1: Assertion at runtime

Notice the truly horrid caption of the message box that displays the assertion, which tells you that the buttons do something other than what their captions might lead you to believe. Apparently Microsoft decided to overload the existing system message box API rather than design some custom user interface for assertions. Don’t do this in your own code!

Note

In Chapter 5, “Preventing Bugs with Unit Testing,” you’ll learn about a superior alternative to quick and dirty testing applications.

Mechanics of .NET Assertions

As I mentioned earlier, you can use either the Trace.Assert or the Debug.Assert method in your code. These classes are identical, except that the Debug class is active only if you define the DEBUG symbol in your project, and the Trace class is active only if you define the TRACE symbol in your project. These constants are commonly defined in the Build section of the project properties (available by right-clicking on the project node in Solution Explorer and selecting Properties), as shown in Figure 4.2.

click to expand
Figure 4.2: Defining build constants

By default, the Debug configuration defines both the TRACE and DEBUG constants, and the Release configuration defines the TRACE constant. Thus, Trace.Assert is active in both default configurations, whereas Debug.Assert is active only in the Debug configuration. I tend to use Debug.Assert because I usually reserve assertions for design-time debugging rather than end-user error logging. Because assertions are an aid for the developer rather than the end user, I don’t believe they have a place in release code. Instead, code you release to other users should implement an error-handling and logging strategy that precludes the necessity for any assertions.

Note

I’ll talk more about error-logging strategies in Chapter 10, “Logging Application Activity.”

The Trace and Debug classes can send their output to more than one place. There are special classes within the System.Diagnostics namespace called Listener classes. These classes are responsible for forwarding, recording, or displaying the messages generated by the Trace and Debug classes. The Trace and Debug classes have a Listeners property, which is a collection capable of holding objects of any type derived from the TraceListener class. The TraceListener class is an abstract class that belongs to the System.Diagnostics namespace, and it has three implementations:

  • DefaultTraceListener An object of this class is automatically added to the Listeners collection of the Trace and Debug classes. Its behavior is to write messages to the Output window or to message boxes, as you saw in Figure 4.1.

  • TextWriterTraceListener An object of this class writes messages to any class that derives from the Stream class. You can use a TextWriterTraceListener object to write messages to the console or to a file.

  • EventLogTraceListener An object of this class writes messages to the Windows event log.

You can also create your own class that inherits from the TraceListener class if you want custom behavior. You could, for example, export all assertion results to an XML file. When doing so, you must at least implement the Write and WriteLine methods.

start sidebar
Assertions in Other Languages

Most modern computer languages have some built-in assertion facility similar to the Trace and Debug support in .NET. Even if your language of choice doesn’t support assertions, it’s easy enough to add them by writing a small bit of code. In general, you’d use a structure something like this:

 Public Sub Assert (Assertion As Boolean, Message As String)      If Not Assertion Then          MsgBox Message, "Assertion Failed"          Stop      End If  End Sub 

That is, if the asserted condition is false, you want to display the message and halt the program. Otherwise, the procedure will return to the calling code.

end sidebar

Guidelines for Good Assertions

Generally, you don’t want end users to see the results of assertions. Ideally, you’ll fix any problems that lead to assertions firing before you ship the code, but even if you don’t manage to do that, you should develop a friendlier strategy for end users than just dumping stack traces on their screen. That’s why I suggest using Debug.Assert rather than Trace.Assert in most cases. Assuming that you’re shipping the release configuration to your users (and you should), debug assertions will simply vanish when you compile the code.

That leads to another guideline for the good use of assertions: Make sure assertions don’t have side effects. Assertions should check that something is true, not execute any other code. For example, here’s a bad use of an assertion:

 // Make sure path is OK  Debug.Assert(      (newPath = Path.Combine(foldername, filename)) != string.Empty,      "Bad path to download"); 

See the problem? If you take out the assertion, then newPath will never get initialized. Putting executable statements into assertions is a good way to introduce such mysterious errors when you switch from debug to release builds.

Another mistake is to use assertions to verify that your compiler and language are working properly:

 int[] intSizes = new int[3];  Debug.Assert(intSizes.GetUpperBound(0) == 2,      "Failed to initialize array"); 

If you can’t trust your compiler to allocate three array members when you tell it to declare an array with three members, you might as well just give up now. No amount of assertions will make your applications any more reliable.

So what should you use assertions for? One excellent use for assertions is to verify that input data is reasonable. If you refer back to the first code snippet in this chapter, you’ll see that is what I’m doing with the SourceUrl argument. Note that I’m checking in the method that uses the value, not in the method that sets the value. By placing the assertion here, I can make sure that any code that calls this method is using reasonable values.

Another good use for assertions is to check your assumptions after a particularly involved or tricky piece of code executes. For example, you might make some calculations that are designed to yield a percentage based on a complex set of inputs. When the calculations have finished, you could use an assertion to verify that the final result is between zero and one.

RULE

Avoid writing involved or tricky code. There are no points for being exceptionally clever in the real world—only for writing code that works the way it should.

Exceptions

Exceptions represent another way to handle problems with your code. As the name implies, exceptions are for exceptional situations—primarily errors that cannot be avoided at runtime.

For example, consider the DownloadEngine component within Download Tracker. I’m developing this as a general-purpose library that any application can use. In particular, the application can pass in a URL to be downloaded. But there’s no way for the DownloadEngine component to prevent the calling application from passing in “Mary had a little lamb” instead of a valid URL. So what should the component do when that happens? That’s where exceptions come into play. An exception signals an exceptional problem in your code that will be handled in another part of your code. As an example, the DownloadEngine component uses this code structure to handle problems with the actual downloading process:

 public void GetDownload(Download d)  {      string filename = string.Empty;      string foldername = string.Empty;      WebClient wc = new WebClient();      try      {          // Code to perform the actual download goes here      }      catch (Exception e)      {          // Bubble any exception up to caller, with custom info          throw new DownloadException("Unable to download" ,d, e);      }      finally      {          wc.Dispose();      }  } 

Note

In the case of Download Tracker, of course, I’m in control of the input layer as well as the library. That means that I can prevent this exception from happening in many cases by checking the input. For example, I can use a .NET regular expression to make sure that the URL typed by the user is actually a URL. Generally, it’s good programming practice to catch errors as soon as possible. But it doesn’t hurt to have a backup plan, as in this case.

Exception Mechanics

You should already be familiar with the coding mechanics of exceptions in C#, but here’s a quick review just in case. The following four statements together make up the exception-handling mechanism:

  • The try statement indicates the start of an exception-handling block. If any code within the block raises an exception, that exception is handled by the associated catch statement.

  • The catch statement indicates the start of the actual exception handling. The CLR transfers execution here if an exception is raised in the associated exception-handling block.

  • The finally statement indicates the start of code that will always run, whether or not an exception occurs. Typically, the finally block is used for cleanup.

  • The throw statement is used to generate an exception. If you throw an exception in a catch block, it will be handled by the parent routine’s exception-handling block (if any).

So, the DownloadEngine component takes a relatively straightforward approach to exception handling. It catches any exception in its own operations, wraps this exception in a new custom exception (more on that in a moment), and throws it to the parent code. This is a typical pattern for library code, which doesn’t have a user interface to present errors directly to the end user. Ideally, every user interface component will validate the input so that such errors never happen. But as a library writer, you can never be quite sure that’s going to happen (unless the library is purely for internal use, and you have control over every single application that calls the library).

Custom Exceptions

You’ll want to create custom exception classes in two situations:

  1. There is no existing exception class that correctly represents the exceptional condition.

  2. You want to pass additional information to the parent code with the exception.

In the case of the DownloadEngine component, I want to pass the actual failing Download object back to the caller so that it can determine which download failed. Thus, I’ve created a custom DownloadException class in my code.

Tip

To quickly review the built-in exception classes in the .NET Framework, use the Object Browser or a tool such as Lutz Roeder’s Reflector (www.aisto.com/roeder/dotnet/) to search for classes containing the word “Exception” in their names.

Here are some guidelines that you should follow when creating a custom exception class:

  • Derive your exception classes from the System.ApplicationException class.

  • End the name of custom exception class with the word Exception.

  • Implement three constructors with the signatures shown in the following code:

 public class MyOwnException : System.Exception  {      public MyOwnException() : base()      {      }      public MyOwnException(string message) : base(message)      {      }      public MyOwnException(string message, System.Exception e) :      base(message, e)      {      }  } 

Here’s the code for the DownloadException class. In addition to implementing the required constructors, it supplies two other constructors that accept a Download object.

 /// <summary>  ///     Custom exception class to handle download errors  /// </summary>  /// <remarks>  ///     Additional fields:  ///         d: The failing download to pass with the exception  /// </remarks>  public class DownloadException : System.Exception  {      // Define the additional fields      private Download _d;      // Define read-only properties for the additional fields      /// <summary>      ///     The associated Download object      /// </summary>      public Download d      {          get          {              return _d;          }      }      /// <summary>      ///     Parameterless (default) constructor      /// </summary>      public DownloadException() : base()      {      }      /// <summary>      ///     Constructor for an exception with text message      /// </summary>      public DownloadException(string message) : base(message)      {      }      /// <summary>      ///     Constructor for an exception with text      ///     message and inner exception      /// </summary>      public DownloadException(string message,          System.Exception e) : base(message, e)      {      }      /// <summary>      ///     Constructor for an exception with text      ///     message and Download object      /// </summary>      public DownloadException (          string message, Download d) : this(message)      {          _d = d;      }      /// <summary>      ///     Constructor for an exception with text      ///     message, inner exception and Download object      /// </summary>      public DownloadException(          string message, Download d, System.Exception e) :          this(message, e)      {          _d = d;      }  } 

Note

The comment lines starting with /// are C# XML comments for the benefit of documentation generators. Other languages might offer their own syntax for such comments. I’ll discuss this further in Chapter 12, “Creating Documentation.”

Guidelines for Good Exceptions

In addition to the rules for creating exception classes, other guidelines you should keep in mind when using exceptions in your code include the following:

  • Exceptions are for exceptional situations. Don’t use exceptions as an alternative to other control-of-flow statements. As with other software tools, many developers have a tendency to overuse exceptions when they’re first exposed to the concept. Don’t, for example, use an exception to signal to the calling code that a download was completed successfully. That’s what the return value from your method is for. Alternatively, your method could raise an event for the calling code to handle.

  • In general, you should use the exception constructors that include a string value for additional information. This approach lets the calling application display the string to the end user if it can’t figure out what else to do with the exception.

  • If you’re throwing an exception to pass error information from lower-level code to higher-level code, use one of the constructors that accepts an Exception object. This lets you wrap the exception that you received. The calling code can drill into that exception, if necessary, by using the InnerException property of your exception class.

  • Don’t create custom exceptions when the built-in ones will do. For example, you should use the build-in InvalidOperationException if calling code attempts to set a property before your class is completely initialized, or ArgumentException if inputs are out of the allowed range.



 < Day Day Up > 



Coder to Developer. Tools and Strategies for Delivering Your Software
Coder to Developer: Tools and Strategies for Delivering Your Software
ISBN: 078214327X
EAN: 2147483647
Year: 2003
Pages: 118

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net