Planning for the Unexpected - JScript .NET and Exceptions

The .NET Class Library uses exceptions throughout to report errors or unexpected conditions; however, a lot of code isn't exception aware. This article explains what exceptions are, how to respond to exceptions, and several ways of creating your own exceptions.

What's an Exception?

Here's a one sentence description of an exception: an exception is a means of control designed to keep an application's data in a consistent state during unexpected circumstances. In plain English this means that exceptions work to make your applications more reliable in three ways:

  • Increase the visibility of errors
  • Provide a centralized means of handling errors
  • Provide a means of handling errors where most appropriate

Your application is considered to be in a consistent state when the data it maintains (its own variables, data in a database, etc) is free of unusual or invalid values. For example, when you withdraw money from your bank account, the bank's accounting system makes two entries: debit your personal account and credit the bank's cash/income account. If the system makes only one entry (for example, debit your personal account) and then crashes the bank's data is considered to be in an inconsistent state because only it recorded only half of a complete transaction (an invalid entry).

A 15 second accounting lesson

If you're not familiar with double entry accounting, read this section.

From the bank's perspective, the money in your account is a liability (they owe you the money in your bank account); you decrease the value of a liability by using a debit transaction. When you put money into the bank you increase the bank's liability to you (they owe you more money), which translates into a credit. When you take money out, you reduce the bank's liability, which translates into a debit transaction. Since most accounting systems are based on the double entry system, where debits equal credits, a bank withdrawal must involve some other account (in this case, an asset account). You reduce the value of an asset using a credit - thus the credit to the bank's cash account.

Exceptions make errors more visible

One of the ways that exceptions work to make your applications more reliable is by making errors more visible. For example, when you allocate some memory using the JScript .NET new operator the system looks for a free block of memory and makes it available to your application. What would be an appropriate response from the system when it cannot accommodate your request? Should it return null, 0, or should it return something like a negative number? Regardless of what the system does, chances are that most developers will write code like this:

var myMessage : String = new String("Hello World");
// new never fails since my system has 512Mb of memory...
Console.WriteLine(myMessage);

Although most developers agree that something could go wrong with the preceding code, many of us don't bother to check the result of every action. Ignoring return values can lead to problems regardless of what platform or programming language you use and exceptions simply work to make errors more visible. The .NET Framework could throw an OutOfMemoryException if you run the preceding fragment on a system that cannot allocate any more memory. When your application does not handle, or respond, to an exception on its own, the .NET Framework handles it for you by promptly terminating your application. The .NET Framework's default exception handler terminates your application because no data is better than bad data. If you don't like the default behavior you'll be interested in the rest of this article, otherwise be prepared to handle calls from your customers about a big message box appearing on their screen when they try to use your application.

Exceptions provide a centralized means of handling errors

If you have been programming for a while, you'll probably be familiar with code that's full with if statements that check the result of every function call, operations, and any other code that could result in a failure. While the approach is good, represents a double-edged sword because the code becomes more reliable at the cost of increasing the code's complexity which makes it more difficult to maintain - complex and difficult to maintain code are factors that eventually lead to the a decrease in an application's reliability.

Statements like if and switch are considered to be local control structures since both statements make a decision and take action within the same scope. For example, consider the following JScript .NET fragment:

if ( myMessage.Length == 0) {
  // do something
}
else {
  // do something else
}
// rest of program here...

The if statement determines of the Length property of the myMessage object is zero and takes an action immediately following the statement. This is a local control structure because the code that makes the decision and the code that carries out the resulting action reside within the same scope (area of the application). Exceptions, in contrast, are non-local control structures. For example, consider the following fragment:

if (myMessage.Length == 0)
  throw InvalidArgumentException();
// rest of program follows...

The code still evaluates the Length of the myMessage object; however, it does not handle the error condition locally - it instead passes control to some other part of the application using the throw statement. When the throw statement executes, the .NET Runtime intervenes and queries the application for a suitable error handler (I'll discuss how to create an error handler shortly) - if the application cannot handle the error then the .NET Runtime invokes its own default error handler (which terminates your application).

If you want to check for certain conditions throughout your application using if statements, you have no choice but to sprinkle all of your code with if statements thereby distributing error handling throughout your code. Using an exception allows you to centralize error handling making it easier to figure out where error handlers are and making them easier to maintain without increasing the complexity of your application.

Exceptions provide a means of handling errors where most appropriate

Assume that the fragment that uses the if statement resides deep in some part of

your application. When the statement executes, chances are that you'll want to notify the user or ask the user to correct the problem; the problem with this approach is that you have to "bubble up" the error condition to some part of your application that can handle the error using some type of protocol you devise yourself. The following fragment illustrates how a developer could handle errors that occur deep within an application:

function main()
{
var userInput : Appointment;
  userInput = getInformationFromUser();
  while(!writeInformationToDatabase(userInput)) {
    userInput = getInformationFromUser();
    if (userInput.Action == action.Exit)
      break;
  }
}

function writeInformationToDatabase(appointmentInfo : Appointment) {
  //..
  if(!connectToDatabase()) {
    MessageBox.Show("Cannot connect to database!");
    return false;
  }
  if(!saveToDatabase(appointmentInfo)) {
    MessageBox.Show("Database not available");
    return false;
  }
  if(!disconnectDatabase()) {
    MessageBox.Show("Cannot disconnect from database!");
    return false;
  }
  return true;  
}

function saveToDatabase(appointmentInfo : Appointment) {

  //..
    return false;
}

The fragment shows an error occurs in the saveToDatabase function, which gets passed on to the writeInformationToDatabase function, and finally ends up in main. Using this approach requires that all functions understand how error reporting works within the application - information that's often irrelevant to the functionality they implement. Exceptions allow you to centralize error reporting into a single location, as shown in the following fragment:

function main()
{
var userInput : Appointment;
  try {
    userInput = getInformationFromUser();
    writeInformationToDatabase(userInput);
  }
  catch ( argException : ArgumentException) {
    //...
  }
  catch ( dbException : databaseConnectionException ) {
    //...
  }
  catch ( e : Exception ) {
    //...
  }
}

function writeInformationToDatabase(appointmentInfo : Appointment) {
  //..
  connectToDatabase()) {
  saveToDatabase(appointmentInfo());
  disconnectDatabase()
  return true;  
}

function saveToDatabase(appointmentInfo : Appointment) {
  //..
  throw (new databaseConnectionException());
}

The fragment shows that the main function is responsible for handling all errors - the rest of the application either throws new exceptions, or simply executes its own functionality. When an exception occurs, the .NET Runtime bubbles-up the exception through the application until it finds a catch statement that's capable of handling the exception (in the main function).

Handling Exceptions

It's easier to understand exceptions by first learning how to handle them. All .NET exceptions derive from the System.Exception class; a class that's designed to be used for all .NET Framework-based exceptions. As previously discussed, when an exception occurs (any exception, not just ones that the .NET Framework generates) the .NET Runtime intervenes and looks through your application's code for a handler that's willing to process (handle) the exception. If the framework does not find an appropriate handler it invokes its own default handler, which terminates your application. The reason I mentioned that all .NET-based exceptions derive from a single class is that it is important how you order your exception handlers in your code - I'll describe this further in a moment.

There are two statements that you initially use to work with exceptions: try and catch. Here's an example of how to use both statements:

var s : StringBuilder; 
try
{
   s = new StringBuilder(1000);
}
catch (argException : ArgumentOutOfRangeException)
{
   //...
}
catch (memException : OutOfMemoryException)
{
   //...
}
catch (genericException : Exception)
{
   //...
}

The try statement delimits a section of code which anticipates that an exception could occur. A catch statement delimits a block of code that's capable of handling a specific type of exception, as defined by its single parameter. When code within a try block encounters and exception, the .NET Runtime inspects the exception to determine its type and reviews each catch statement's parameter to determine if it can find a match. Note the order in which the catch statements appear - they are written from the most specific type of exception (the first catch statement in the listing) to the least specific type (the last catch statement in the listing). The reason that the catch statements are ordered in this way relates to classes and inheritance. Recall that you can treat inherited types as if they are base types (in other words, you can replace a derived type with a base type). If you reverse the order of the catch statements in the listing (by putting the last catch statement first), only the first statement will ever execute because all .NET-based exceptions derive from the System.Exception class.

Using the finally statement

There is another aspect of the try and catch statements called the finally statement. The role of finally statement is to execute unconditionally after the code in a try block or a try and catch block executes, as shown in the following figure:

The finally bock of code is guaranteed to execute in all cases, so it is very useful for releasing resources like database connections or objects that use a lot of memory.

Raising an exception - the throw statement

There are cases where your code cannot handle an exception. For example, you write a catch block to catch the System.Exception type; however, on closer inspection you realize that you cannot handle the exception. The way to handle a situation like this is simply to re-raise the exception to allow the runtime to look through the rest of your code to see if there's another handler capable of handling the exception. You re-raise an exception using the throw statement, as shown in the following listing:

try 
{
   //...
}
catch ( generalException : Exception)
{
   // Cannot deal with the problem here...
   throw generalException;  
}

The throw statement takes a single parameter: the type of exception you want to raise. You can use the new operator to create an object before you throw it; however, you run the risk of not catching an OutOfMemory exception in case the system is low on memory.

Creating your own exception types

The .NET Class Library includes a class called System.ApplicationException which is reserved for applications - the .NET Framework does not raise any exceptions based on this type; however this class also derives from System.Exception so you still have to order your catch statements properly. The following listing demonstrates how to create, throw, and handle your own exception types:

class myInvalidArgumentException extends System.ApplicationException
{

  function myInvalidArgumentException() 
  {
    // default constuctor
  }

  function myInvalidArgumentException( msg : String ) 
  {
    // create a new exception based on a message
  }

  function myInvalidArgumentException( msg : String , innerEx : Exception ) 
  {
    // This constructor gets called as part of a chain of exceptions.
    // The innterEx parameter gets populated with another exception
    // in the chain of exceptions
  }
}

function buggyFunction()
{
  try
  {
    doSomethingBad();
  }
  catch (myException : myInvalidArgumentException)
  {
    //...
  }
  catch (allOtherExceptions : Exception)
  {
    // catch all other exceptions here...
  }
}


function doSomethingBad()
{
  throw (new myInvalidArgumentException("This is really bad");
}

The listing demonstrates how to create a custom exception by using the extends keyword to derive the new class from the System.ApplicationException class. The class has three constructors, as shown in the listing. The first two constructors should be present for all exception classes you create since it makes them easy to use with the new operator. The last constructor is there to accommodate exceptions that exist as part of a chain of exceptions. The buggyFunction demonstrates how to handle both the application-specific exception and all other exceptions. The doSomethingBad function demonstrates how to create an exception based on a String-based message, using the class's second constructor.

Summary

This article gave you an overview of what exceptions are, what they're used for, how to handle them, and how to create your own exceptions. Exceptions are a broad field - there are entire books that cover just exceptions. One excellent resource, for C++ programmers, is "Exceptional C++" by Herb Sutter (ISBN 0201615622) - this is a great resource for C++ programmers who use JScript .NET.


Essam Ahmed is the author of "JScript .NET Programming" (ISBN 0764548689, Published by Hungry Minds September 2001), many articles (including some at CodeGuru.com) and book reviews (also available at CodeGuru.com). Contact Essam at essam@designs2solutions.com, or at his Web site



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: December 11, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Market pressures to move more quickly and develop innovative applications are forcing organizations to rethink how they develop and release applications. The combination of public clouds and physical back-end infrastructures are a means to get applications out faster. However, these hybrid solutions complicate DevOps adoption, with application delivery pipelines that span across complex hybrid cloud and non-cloud environments. Check out this …

  • Due to internal controls and regulations, the amount of long term archival data is increasing every year. Since magnetic tape does not need to be periodically operated or connected to a power source, there will be no data loss because of performance degradation due to the drive actuator. Read this white paper to learn about a series of tests that determined magnetic tape is a reliable long-term storage solution for up to 30 years.

Most Popular Programming Stories

More for Developers

RSS Feeds