Debugging, Asserting , and Tracing
Debugging, asserting, and tracing are still mainstays of application debugging. These capabilities are tucked away in the System.Diagnostics namespace. The System.Diagnostics.Debug.Write and System.Diagnostics.Debug.WriteLine statements replace Debug.Print from Visual Basic 6. The Debug.Assert shared method raises an exception if a condition fails. Assert is customarily used to ensure that application invariants ”things that must be true about your application ” remain true while your application runs. The System.Diagnostics.Trace class supports sending application trace information to a variety of locations.
Using the Debug Class
The Debug class is used primarily to send information to the Debug Output window while you are debugging your application in the IDE. There are also a couple of other capabilities like Assert (which we will talk about next ) and a Listeners collection (see the Programming with Trace Listeners section later in the chapter). The Listeners collection allows you to define an Output window of your own. These Output windows are referred to as listeners .
Debug.Write and Debug.WriteLine are shared methods that facilitate keeping track of your application as it progresses without constantly breaking all the time. The benefit of using built-in capabilities like the Debug class is that Visual Studio .NET automatically manages the behavior in the design-time environment for you. For example, you can write Debug.WriteLine statements and leave them in place. When the DEBUG constant is defined and you are running your code in Visual Studio .NET, the Debug class will send information to the Output window. When you configure your application for release or run it outside the IDE, the Debug code is benign . The DEBUG (and TRACE ) constants are defined in the Build Property Pages (Figure 17.8).
Figure 17.8. Use the Build Property Pages to manage conditional compiler constants like DEBUG and TRACE .
The Debug class is easy to use. Add a Debug.WriteLine statement and send any text you want displayed in the Output window to the WriteLine statement. For example, to use the Debug class in the Calculator application I could add a WriteLine statement after line 14 (in the originally blank line 15) in Listing 17.1. The following statement sends the value of the button's Tag property to the Output window. Figure 17.9 shows the results in the Output window after several buttons have been pressed.
Debug.WriteLine("Key:" + CType(sender, Button).Tag.ToString())
Figure 17.9. The Output window after pressing the 3, 6, 5, 4, 7, 8, and 9 buttons while the Debug.WriteLine statement is in place.
Using the Debug.Assert Method
A favorite quote of mine is from the book No Bugs! by Dave Thielen : "Assert the world!" If you understand what an assertion is, this makes sense to a certain degree.
The Assert method behaves precisely as the word assert suggests. Assert implies "to make sure," so applying computer-speak, assert means "to ensure that application invariants are true." To put it in a more colloquial vernacular, we use the Assert method to make sure that what we think about the state of the application is true, and if it is not, we want to know. Assert does this for us.
The Assert method in its simplest form accepts an expression and tells us when that expression evaluates to False . When told that our predetermined assumptions are false, we know that we have a logic error in the application. Thus, "Assert the world!" makes sense. For the Computer application, I could add an assertion to ensure that Number_Click gets only numbers between 0 and 9 inclusive. Listing 17.2 shows the revised Number_Click event handler with the assertion.
Listing 17.2 Adding an Assertion to a Method
Private Sub Number_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button9.Click, _ Button1.Click, Button2.Click, Button3.Click, _ Button4.Click, Button5.Click, Button6.Click, _ Button7.Click, Button8.Click, Button10.Click Calculator.SendKey(CType(sender, Button).Tag.ToString()) Debug.WriteLine("Key:" + CType(sender, Button).Tag.ToString()) Debug.Assert(CType(sender, Button).Tag >= 0 And CType(sender, Button).Tag <= 9) End Sub
The meaning of the assertion is that the Tag value must be between 0 and 9 inclusive. If I accidentally assigned a nonnumeric button to this event or assigned a bad value to the Tag property, the Assert method would display an exception dialog with some options for continuing or stopping to resolve the problem.
If we want the Assertion Failed dialog (Figure 17.10) to contain additional, pointed information, we can add a text argument. For example, I could add the text "Bad Number Key" to the Assert method in Listing 17.2. This information would be displayed in the dialog, perhaps leading to a more pointed meaning of the stack trace.
Figure 17.10. When an Assert method fails, a dialog showing the stack trace preceding the Assert statement is displayed by default.
With a modicum of creativity you can let your application do the hard work of debugging for you. A good strategy is to add calls to Assert as you are initially writing your methods. The first time you write a method you are likely to have greater insight about your perceived application's state than at any other time, other than when something goes wrong. However, if you use Assert at critical points, you will be able to quickly resolve inconsistencies between your assumptions and reality.
Using the Trace Class
The Trace class plays a cousin role to the Debug class. Trace is used to trace your code as it executes. There is a certain degree of overlap in the way that Trace and Debug are implemented in .NET because the relative capabilities are highly similar. To simplify the differences, think of Debug.WriteLine statements and the Debug class as code meant for your use as a developer. Assume that the Trace.WriteLine method and the Trace class exist to help you diagnose a problem after your application has been deployed.
I use Trace to send information about the execution of my application to a log or some external resource, and I use the Debug.WriteLine and Debug.Assert methods to provide me with an early warning about faulty assumptions. Note that this is a convention I follow out of habit based on older implementations of Assert and Trace rather than existing implementations of the Trace and Debug classes in .NET. Use Debug and Trace to your best advantage in .NET. Experimentation and circumstance will provide the best instruction.