Preventing Bugs
Many programmers believe that the way to make a program robust is to make it able to
continue running even if it encounters errors. For example, consider the following version
of the
Factorial method:
// Recursively calculate n!
private long Factorial(long n)
{
if (n <= 1) return 1;
return n * Factorial(n - 1);
}
This method is robust in the sense that it can handle nonsensical inputs such as –10. The function
cannot calculate –10!, but at least it doesn’t crash so you might think this is a safe method.
Unfortunately while the function doesnt crash on this input, it also doesn’t return a correct
result because –10! is not defined. That makes the program continue running even though it
has produced an incorrect result.
The method also has a problem if its input is greater than 20. In that case, the result is too
big to fit in the
long data type so the calculations cause an integer overflow. By default, the
program silently ignores the error, and the result you get uses whatever bits are left after the
overflow. In this case, the result looks like a large negative number. Again the method doesn’t
crash but it doesn’t return a useful result, either.
In general, bugs that cause a program to crash are a lot easier to find and fix than bugs like
this one that produce incorrect results but continue running.
In this lesson, you learn techniques for detecting and correcting bugs. You learn how to make
bugs jump out so they’re easy to fix instead of remaining hidden.
INPUT ASSERTIONS
In C# programming, an assertion is a statement that the code claims is true. If the statement is
false, the program stops running so you can decide whether a bug occurred.
22
596906c22.indd 259 4/7/10 12:33:37 PM
260
LESSON 22 Preventing Bugs
One way to make an assertion is to evaluate the statement and, if it is false, throw an error. That
guarantees that the program cannot continue running if the assertion is false.
The following code shows a
Factorial method with assertions. If the method’s parameter is less
than 0 or greater than 20, the code throws an exception.
// Recursively calculate n!
private long Factorial(long n)
{
// Validate the input.
if ((n < 0) || (n > 20))
throw new ArgumentOutOfRangeException(
“n”, “Factorial parameter must be between 0 and 20.”);
if (n <= 1) return 1;
return n * Factorial(n - 1);
}
To make this kind of assertion easier, the .NET Framework provides a Debug class. The Debug
class’s static
Assert method takes as a parameter a Boolean value. If the value is false, Assert
displays an error message showing the program’s stack dump at the time so you can figure out
where the error occurred.
The following code shows a new version of the factorial method that uses
Debug.Assert. The optional
second parameter to
Debug.Assert gives a message that should be displayed if the assertion fails.
// Recursively calculate n!
private long Factorial(long n)
{
// Validate the input.
Debug.Assert((n >= 0) && (n <= 20),
“Factorial parameter must be between 0 and 20.”);
if (n <= 1) return 1;
return n * Factorial(n - 1);
}
The Debug class is in the System.Diagnostics namespace. If you want to use it
without including the namespace, as in the preceding code, you can include the
following
using directive at the top of the file:
using System.Diagnostics;
Normally when you develop a program you make debug builds. These include extra debugging symbols
so you can step through the code in the debugger. If you switch to a release build, those symbols are
omitted, making the compiled program a bit smaller. The
Debug.Assert method also has no effect in
release builds.
596906c22.indd 260 4/7/10 12:33:37 PM
Input Assertions
261
The idea is that you can use Debug.Assert to test the program but then skip the assertions after
the program is debugged and ready for release to the user. Of course this only works if the code is
robust enough to behave correctly even if a bug does slip past the testing process and appears in
the release build. In the case of the
Factorial method, this code must always protect itself against
input errors so it should throw an exception rather than using
Debug.Assert.
To switch from a debug to a release build or vice versa, open the Build menu and select the
Configuration Manager command to display the dialog shown in Figure 22-1. Select Debug or
Release from the dropdown menu and click Close.
FIGURE 221
When you build the program, Visual Studio places the compiled executable in the projects
binDebug or binRelease subdirectory. Be sure you use the correct version or you may find
Debug.Assert statements displaying errors in what you thought was a release build.
The Debug class provides some other handy methods in addition to Assert. The
WriteLine method displays a message in the Output window. You can use it to
display messages showing you what methods are executing, to display param-
eter values, and to give you other information that you might otherwise need to
learn by stepping through the code in the debugger.
The
Debug class’s Indent method lets you change the indentation of output
produced by
Debug.WriteLine so, for example, you can indicate nesting of
method calls.
Like the other Debug methods, these do nothing in release builds so the end user
never sees these messages.
596906c22.indd 261 4/7/10 12:33:38 PM
262
LESSON 22 Preventing Bugs
OTHER ASSERTIONS
In addition to input assertions, a method can make other assertions as it performs calculations. A
method can use assertions to check intermediate results and to validate final results before returning
them. A program can even use assertions to validate the value it receives from another method.
Often these assertions cannot be as exact as those you can perform on inputs but you may still be
able to catch some really ludicrous values.
For example, suppose an order processing form lets the user enter items for purchase and then
calculates the total cost. You could use assertions to verify that the total cost is between $0.01
and $1 million. This is a pretty wide range so you are unlikely to catch any but the most egregious
errors, but you may catch a few.
Note that you should not test user input errors with assertions. An assertion interrupts the program
so you can try to find a bug. Your code should check for user input errors and handle them without
interrupting the program. Remember, when you make a release build,
Debug.Assert calls go away
so you cannot rely on them to help the user enter valid values.
One drawback to assertions is that it’s hard to make programmers use them. When you’re writing
code, it’s hard to convince yourself that the code could be wrong. After all, if you knew there was a
bug in the code, you’d fix it.
Assertions are like seat belts, airbags, and bicycle helmets. You don’t use them because you expect
to need them today; you use them just on the off chance that you’ll need them some day. Usually
your assertions will just sit there doing nothing but if a bug does rear its ugly head a good set of
assertions can make the difference between finding the bug in seconds, hours, or days.
TRY IT
In this Try It, you write a method to calculate a departments
average salary. The interesting part is adding assertions to
make sure the method is being called correctly.
To test the method, you’ll build the program shown in Figure 22-2.
The focus of this Try It is on the method that calculates the average,
not on the user interface. The assumption is that some other part
of a larger program would call this method, so the user interface
shown in Figure 22-2 is purely for testing purposes. A real program
would not allow the user to enter invalid values.
You can download the code and resources for this Try It from the book’s web
page at
www.wrox.com or www.CSharpHelper.com/24hour.html. You can find
them in the Lesson22 folder in the download.
FIGURE 222
596906c22.indd 262 4/7/10 12:33:38 PM
Try It
263
Lesson Requirements
Build a program similar to the one shown in Figure 22-2.
When the user clicks Calculate, make the program split the values entered in the textbox
apart, copy them into an array of decimals, pass them to the AverageSalary method, and
display the result.
Make the
AverageSalary method validate its inputs by asserting that the array has a reason-
able number of elements and that the salaries are reasonable. (Assume you’re not working on
Wall Street so salaries are at least $10,000 and less than $1 million.) Also validate the average.
Hints
Think about how the program should react in a final release build for each of the input
conditions.
For example, if the
values array contains a salary of $1,600, what should the method
do? In this case, that value is unusual but it could be valid (perhaps the company hired an
intern for a week) so the method can calculate a meaningful (although unusual) result. The
method should check this condition with
Debug.Assert so it can calculate a result in the
release version.
For another example, suppose the
values array is empty. In this case the method cannot
calculate a meaningful value so it should throw an exception so the code calling it can deal
with the problem.
Step-by-Step
Build a program similar to the one shown in Figure 22-2.
1. This is reasonably straightforward.
When the user clicks Calculate, make the program split the values entered in the textbox
apart, copy them into an array of decimals, pass them to the AverageSalary method, and
display the result.
1. You can use code similar to the following:
// Calculate and display the average salary.
private void calculateButton_Click(object sender, EventArgs e)
{
try
{
// Copy the salaries into an array.
string[] string_salaries = salariesTextBox.Text.Split();
decimal[] salaries = new decimal[string_salaries.Length];
for (int i = 0; i < string_salaries.Length; i++)
{
salaries[i] =
decimal.Parse(string_salaries[i], NumberStyles.Any);
}
596906c22.indd 263 4/7/10 12:33:39 PM
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.139.103.204