Visual Basic

In-Flight Testing: Using the Assert Statement

One of the most powerful debugging tools available, at least to C programmers, is the Assert macro. Simple in concept, it allows a programmer to write self-checking code by providing an easy method of verifying that a particular condition or assumption is true. Visual Basic programmers had no structured way of doing this until Visual Basic 5. Now we can write statements like this:

  Debug.Assert 2 + 2 = 4
  Debug.Assert bFunctionIsArrayHealthy
  Select Case iUserChoice
      Case 1
          DoSomething1
      Case 2
          DoSomething2
      Case Else
          ' We should never reach here!
          Debug.Assert nUserChoice = 1 Or nUserChoice = 2
  End Select

Debug.Assert operates in the development environment only-conditional compilation automatically drops it from the compiled EXE. It will take any expression that evaluates to either TRUE or FALSE and then drop into break mode at the point the assertion is made if that expression evaluates to FALSE. The idea is to allow you to catch bugs and other problems early by verifying that your assumptions about your program and its environment are true. You can load your program code with debug checks; in fact, you can create code that checks itself while running. Holes in your algorithms, invalid assumptions, creaky data structures, and invalid procedure arguments can all be found in flight and without any human intervention.

The power of assertions is limited only by your imagination. Suppose you were using Visual Basic 6 to control the space shuttle. (We can dream, can't we?) You might have a procedure that shuts down the shuttle's main engine in the event of an emergency, perhaps preparing to jettison the engine entirely. You would want to ensure that the shutdown had worked before the jettison took place, so the procedure for doing this would need to return some sort of status code. To check that the shutdown procedure was working correctly during debugging, you might want to perform a different version of it as well and then verify that both routines left the main engine in the same state. It is fairly common to code any mission-critical system features in this manner. The results of the two different algorithms can be checked against each other, a practice that would fail only in the relatively unlikely situation of both the algorithms having the same bug. The Visual Basic 6 code for such testing might look something like this:

  ' Normal shutdown
  Set nResultOne = ShutdownTypeOne(objEngineCurrentState)
  ' Different shutdown
  Set nResultTwo = ShutdownTypeTwo(objEngineCurrentState)
  ' Check that both shutdowns produced the same result.
  Debug.Assert nResultOne = nResultTwo

When this code was released into production, you would obviously want to remove everything except the call to the normal shutdown routine and let Visual Basic 6's automatic conditional compilation drop the Debug.Assert statement.

You can also run periodic health checks on the major data structures in your programs, looking for uninitialized or null values, holes in arrays, and other nasty gremlins:

  Debug.Assert bIsArrayHealthy CriticalArray

Assertions and Debug.Assert are designed for the development environment only. In the development environment, you are trading program size and speed for debug information. Once your code has reached production, the assumption is that it's been tested well and that assertions are no longer necessary. Assertions are for use during development to help prevent developers from creating bugs. On the other hand, other techniques-such as error handling or defensive programming-attempt to prevent data loss or other undesirable effects as a result of bugs that already exist.

Also, experience shows that a system loaded with assertions can run from 20 to 50 percent slower than one without the assertions, which is obviously not suitable in a production environment. But because the Debug.Assert statements remain in your source code, they will automatically be used again whenever your code is changed and retested in the development environment. In effect, your assertions are immortal-which is as it should be. One of the hardest trails for a maintenance programmer to follow is the one left by your own assumptions about the state of your program. Although we all try to avoid code dependencies and subtle assumptions when we're designing and writing our code, they invariably tend to creep in. Real life demands compromise, and the best-laid code design has to cope with some irregularities and subtleties. Now your assertion statements can act as beacons, showing the people who come after you what you were worried about when you wrote a particular section of code. Doesn't that give you a little frisson?

Another reason why Debug.Assert is an important new tool in your fight against bugs is the inexorable rise of object-oriented programming. A large part of object-oriented programming is what I call "design by contract." This is where you design and implement an object hierarchy in your Visual Basic program, and expose methods and properties of your objects for other developers (or yourself) to use. In effect, you're making a contract with these users of your program. If they invoke your methods and properties correctly, perhaps in a specific order or only under certain conditions, they will receive the services or results that they want. Now you are able to use assertions to ensure that your methods are called in the correct order, perhaps, or that the class initialization method has been invoked before any other method. Whenever you want to confirm that your class object is being used correctly and is in an internally consistent state, you can simply call a method private to that class that can then perform the series of assertions that make up the "health check."

One situation in which to be careful occurs when you're using Debug.Assert to invoke a procedure. You need to bear in mind that any such invocation will never be performed in the compiled version of your program. If you copy the following code into an empty project, you can see clearly what will happen:

  Option Explicit
  Dim mbIsThisDev As Boolean
  Private Sub Form_Load()
  mbIsThisDev = False
  ' If the following line executes, the MsgBox will display
  ' True in answer to its title "Is this development?"
  ' If it doesn't execute, the MsgBox will display false.
  Debug.Assert SetDevFlagToTrue
  MsgBox mbIsThisDev, vbOKOnly, "Is this development?"
  Unload Me
  End Sub
  Private Function SetDevFlagToTrue() As Boolean
  SetDevFlagToTrue = True
  mbIsThisDev = True
  End Function

When you run this code in the Visual Basic environment, the message box will state that it's true that your program is running within the Visual Basic IDE because the SetDevFlagToTrue function will be invoked. If you compile the code into an EXE, however, the message box will show FALSE. In other words, the SetDevFlagToTrue function is not invoked at all. Offhand, I can't think of a more roundabout method of discovering whether you're running as an EXE or in the Visual Basic 6 IDE.