Verification of Program Correctness..
When writing a program, we often make
mistakes and have to take time to debug
it. Syntax errors are caught and
flagged by a good compiler. Run-time
errors that abort the program or that produce wrong answers are usually found by
testing the program using a test data set.
A good test data set should include examples of all possible data
items. For example, test data for the
postfix stack evaluation algorithm should contain expressions using all
possible operators and operands. In
addition all possible ways of making an expression invalid should be
illustrated.
The larger a program gets, the more varied
are the paths through it and the more complicated the test data becomes. It is then easy to overlook some section of
the program or some possible input to it.
While a number of computer errors
publicized in the media are actually human data entry errors, many are due to
unresolved bugs in programs. These are
usually in sections only entered occasionally.
Computer vendors such as Microsoft simply assume that their very large
system programs contain a lot of bugs.
As these systems are used, bugs are discovered and (hopefully)
corrected. However, usually before they
are all found, a new system is released with the potential for many new
bugs. Software development is a
continuing process. Except for very
short programs, no one claims their work is error-free. We can prove the correctness of short
programs. The method is well
understood, but it is long and tedious.
Furthermore, if the program is of moderate size, the proof becomes so
long and complicated, that a person can't follow it. It requires a computer program to check the proof, and this
program itself is then subject to error.
Currently, there is no way out of this corner, but scientists continue
to work on the problem. We will only look briefly at proofs of correctness,
just enough so that you know what they are.
For non-iterative code, we use assertions. These are of the form
{precondition} program
{postcondition}
where we assert: If the precondition is true before execution of the program, then
the postcondition will be true afterwards.
Examples:
//
precondition: count = 1
sum = count ++;
//
postcondition: sum = 2
//
precondition: count = number
number ++;
//
postcondition: count = number - 1
// no
precondition
if first
< second
{
// first < second
low = first;
high = second;
}
else
{ //
second <= first
low = second;
high = first;
}
// postcondition: low <= high
int temp;
// precondition: (x = a) and (y = b)
{
temp = x; // temp = a
x = y; // x = b
y = temp; // y = a
}
// postcondition: (x = b) and (y = a)
// no precondition
int
temp;
if (mid < low)
{
temp = mid; mid = low; low = temp;
}
//
low <= mid
if (high < mid)
{
temp
= high; high = mid; mid = temp;
}
//
(mid <= high) and (low <= high)
if (mid < low)
{
temp
= mid; mid = low; low = temp;
}
//
postcondition (low <= mid) and (mid <= high)
For iteration, we use conditions called loop invariants. These are more complicated. They must express what the loop is supposed to be doing. Consider a while loop:
// Loop
invariant true, B true (or loop won't be entered)
while (B)
{
// Loop invariant true, B true
S1;
// Loop invariant and B may be false
S2;
// Loop invariant true, B may be true or
false
}
// Loop
invariant true, B false (or loop won’t be left)
Example: Method that computes the nth
power of 2.
// precondition: (n >= 0)
public int powerOfTwo ( int n)
{
int count =
0, product = 1;
// product =
2 count
while (count
< n) do
{
//
(count < n) and (product = 2 count)
product = 2 * product;
//
(count < n) and (product = 2 (count+1))
count
++;
// (count <= n) and (product = 2 count)
}
//
(count = n) and (product = 2 count)
return
product;
} // method powerOfTwo
// postcondition: 2 n returned
The loop invariant is
(product = 2 count).
Note that the loop condition, (count <
n), is true when we enter the loop and false on exit. Also note that if the precondition, (n >= 0), is false, the
loop won't be entered. In that case,
the postcondition, (count = n), will not be true. Finally, such a proof makes the loop condition clear. It should be (count < n) not (count <=
n). The latter condition causes an
off-by-one error or boundary error.
These are common with beginning programmers.