Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Programming
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Databases
Mail Systems
openSolaris
Eclipse Documentation
Techotopia.com
Virtuatopia.com
Answertopia.com

How To Guides
Virtualization
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Windows
Problem Solutions
Privacy Policy

  




 

 

Thinking in C++ Vol 2 - Practical Programming
Prev Home Next

2: Defensive Programming

Writing perfect software may be an elusive goal for developers, but a few defensive techniques, routinely applied, can go a long way toward improving the quality of your code.

Although the complexity of typical production software guarantees that testers will always have a job, we hope you still yearn to produce defect-free software. Object-oriented design techniques do much to corral the difficulty of large projects, but eventually you must write loops and functions. These details of programming in the small become the building blocks of the larger components needed for your designs. If your loops are off by one or your functions calculate the correct values only most of the time, you re in trouble no matter how fancy your overall methodology. In this chapter, you ll see practices that help create robust code regardless of the size of your project.

Your code is, among other things, an expression of your attempt to solve a problem. It should be clear to the reader (including yourself) exactly what you were thinking when you designed that loop. At certain points in your program, you should be able to make bold statements that some condition or other holds. (If you can t, you really haven t yet solved the problem.) Such statements are called invariants, since they should invariably be true at the point where they appear in the code; if not, either your design is faulty, or your code does not accurately reflect your design.

Consider a program that plays the guessing game of Hi-Lo. One person thinks of a number between 1 and 100, and the other person guesses the number. (We ll let the computer do the guessing.) The person who holds the number tells the guesser whether their guess is high, low or correct. The best strategy for the guesser is a binary search, which chooses the midpoint of the range of numbers where the sought-after number resides. The high-low response tells the guesser which half of the list holds the number, and the process repeats, halving the size of the active search range on each iteration. So how do you write a loop to drive the repetition properly? It s not sufficient to just say

bool guessed = false;
while(!guessed) {
...
}
 

because a malicious user might respond deceitfully, and you could spend all day guessing. What assumption, however simple, are you making each time you guess? In other words, what condition should hold by design on each loop iteration?

The simple assumption is that the secret number is within the current active range of unguessed numbers: [1, 100]. Suppose we label the endpoints of the range with the variables low and high. Each time you pass through the loop you need to make sure that if the number was in the range [low, high] at the beginning of the loop, you calculate the new range so that it still contains the number at the end of the current loop iteration.

The goal is to express the loop invariant in code so that a violation can be detected at runtime. Unfortunately, since the computer doesn t know the secret number, you can t express this condition directly in code, but you can at least make a comment to that effect:

while(!guessed) {
// INVARIANT: the number is in the range [low, high]
...
}
 

What happens when the user says that a guess is too high or too low when it isn t? The deception will exclude the secret number from the new subrange. Because one lie always leads to another, eventually your range will diminish to nothing (since you shrink it by half each time and the secret number isn t in there). We can express this condition in the following program:

//: C02:HiLo.cpp {RunByHand}
// Plays the game of Hi-Lo to illustrate a loop invariant.
#include <cstdlib>
#include <iostream>
#include <string>
using namespace std;
 
int main() {
cout << "Think of a number between 1 and 100" << endl
<< "I will make a guess; "
<< "tell me if I'm (H)igh or (L)ow" << endl;
int low = 1, high = 100;
bool guessed = false;
while(!guessed) {
// Invariant: the number is in the range [low, high]
if(low > high) { // Invariant violation
cout << "You cheated! I quit" << endl;
return EXIT_FAILURE;
}
int guess = (low + high) / 2;
cout << "My guess is " << guess << ". ";
cout << "(H)igh, (L)ow, or (E)qual? ";
string response;
cin >> response;
switch(toupper(response[0])) {
case 'H':
high = guess - 1;
break;
case 'L':
low = guess + 1;
break;
case 'E':
guessed = true;
break;
default:
cout << "Invalid response" << endl;
continue;
}
}
cout << "I got it!" << endl;
return EXIT_SUCCESS;
} ///:~
 

The violation of the invariant is detected with the condition if(low > high), because if the user always tells the truth, we will always find the secret number before we run out of guesses.

We also use a standard C technique for reporting program status to the calling context by returning different values from main( ). It is portable to use the statement return 0; to indicate success, but there is no portable value to indicate failure. For this reason we use the macro declared for this purpose in <cstdlib>: EXIT_FAILURE. For consistency, whenever we use EXIT_FAILURE we also use EXIT_SUCCESS, even though the latter is always defined as zero.

Thinking in C++ Vol 2 - Practical Programming
Prev Home Next

 
 
   Reproduced courtesy of Bruce Eckel, MindView, Inc. Design by Interspire