Writing Specifications for Programs
A program is correct when it produces the correct result as specified, so correctness is always measured against a specification.
A Passed Proof Is Not the Finish Line
Imagine a team that has finished writing a program and has shown, with a proof, that the program is correct. It is tempting to stop there and assume the job is done, and that the program can be trusted to do what its users need. This belief sounds reasonable, but it quietly mixes two different questions. The first question is whether the program does what it was asked to do. The second is whether what it was asked to do was the right thing in the first place. Keeping these two questions apart is the starting point for understanding what a correctness proof can and cannot give you.
Before reading on, take a moment to decide for yourself which of the two questions a proof of correctness answers. Commit to an answer in your own mind. The reasoning you do now is what will make the rest of this article stick.
What Correctness Is Measured Against
A program is correct when it produces the correct result as specified. Correctness is therefore always measured against a specification. A program has no correctness in isolation; it is correct or incorrect only relative to the statement of what it should do.
The specification says what the correct result must be. The program is then judged by whether its actual result matches that stated result. This is why the specification is the yardstick for every correctness claim. If the yardstick is wrong, a program can match it perfectly and still fail the people who depend on it.
Read the diagram as a check. The left column is what the specification demands; the right column is what the program actually does. Correctness is the claim that these agree for every run the program can make.
Following State Through a Run
The reasoning behind correctness proofs rests on two fundamental ideas: process and state. The state of a program is all the information relevant at one moment. A process is the sequence of states the computer passes through as the program executes. Understanding both lets you describe exactly what a program does at every moment, and that description is the raw material any proof needs.
To make this concrete, consider a small invented program with two steps. Step one sets a to 4. Step two sets b to the value of a plus 5. Before any step runs, nothing has been set. After step one, the state holds a equal to 4. After step two, the state holds a equal to 4 and b equal to 9. The whole run is the sequence of those states, and the final state is the one a proof would compare against the specification.
What do you think happens?
In the two-step example, what is the value of b in the state immediately after step two runs, given that a is 4 at that point?
Reveal answer
9
Step two sets b to the value of a plus 5. Since a is 4 at that moment, b becomes 4 plus 5, which is 9. The state after step two therefore holds a equal to 4 and b equal to 9.
The Meaning of a Statement Is Its Effect
The meaning of a statement can be expressed as its effect on the computer's state. This matters because each statement's effect is a solid fact you can rely on when building a proof. Instead of asking what a statement vaguely means, you ask exactly what it changes in the state and what it leaves alone.
Comparing the state just before and just after a statement makes the effect visible. Only the values the statement touches should differ between the two snapshots. Everything else in the state stays the same.
Here the statement that sets b changes only b, moving it from 0 to 9. The value of a is untouched in both snapshots. A proof that tracks statements this way can chain each effect onto the next until it reaches the final state.
Why Proofs Reason About Every Run
In some cases it is possible to demonstrate, mathematically, that the sequence of computations a program describes will always produce the correct result. This is the sense in which a program can be provably correct. The proof reasons about every possible run of the program, not just the few runs someone happened to try. That is what makes it stronger than testing alone.
The specification, the program's process of states, the effect of each statement, and the final proof are one chain of reasoning. The specification says what the correct result must be. The execution is a process of successive states. Each statement's effect is a reliable fact. The proof strings these facts together to show that the final state of every run matches what the specification demands. Removing any link weakens the whole argument.
Why Proving Correctness Is Hard
Rigorous proof is genuinely hard. In practice, full mathematical proofs of correctness are typically limited to fairly small programs. The effort of tracking every state and every statement's effect grows quickly as a program gets larger, which is why proofs are rare in everyday work.
This does not make the ideas useless for larger work. Even in everyday programming, the ideas and techniques used in correctness proofs can guide how you reason about code, even when you never write a formal proof. Asking what each statement changes, and what result the program must produce, is useful habit even without a written proof.
When a Correct Program Is Still Useless
A program can exactly meet its specification and still be useless to the people who use it. This happens when the specification itself is wrong. The program then does precisely what was asked, but what was asked was not the right thing. The proof succeeds, and the result is still not what anyone needed.
Mistakes Beginners Make About Correctness
Common mistake Assuming a proven program is finished and can be trusted to meet user needs
Why it failsA proof shows the program matches its specification. It says nothing about whether that specification captured what users actually need.
Do this insteadAfter a proof, still ask whether the specification describes the right goal.
Common mistake Judging correctness without a specification
Why it failsCorrectness is always measured against a specification, so a program has no correctness on its own.
Do this insteadWrite down what the correct result must be before deciding whether the program is correct.
Common mistake Treating testing a few runs as the same as a proof
Why it failsA proof reasons about every possible run of the program, while a handful of tests covers only the runs someone tried.
Do this insteadRecognise that tests build confidence on sample runs, while a proof covers all runs.
Common mistake Thinking a statement's meaning is vague
Why it failsA statement's meaning can be stated precisely as its effect on the computer's state.
Do this insteadCompare the state before and after the statement to see exactly what it changes.
Trace the State Yourself
Extend the two-step example with a third step that sets c to the value of b minus 3. Describe the state after each of the three steps, and state the final value of c. Then decide whether the final state would satisfy a specification that demands c equal 6.
Hints
- Start from the state after step two, where a is 4 and b is 9.
- Compute c by taking the current value of b and subtracting 3, one state at a time.
- Compare the final value of c with the value the specification demands.
Key Takeaways
- A program is correct when it produces the correct result as specified, so correctness is always measured against a specification.
- The state of a program is all relevant information at one moment, and a process is the sequence of states passed through during execution; a statement's meaning is its effect on that state.
- A proof of correctness reasons about every possible run, which makes it stronger than testing, but full proofs are typically limited to fairly small programs.
- A proof is only as trustworthy as its specification, so a program can be correct and still useless when the specification describes the wrong goal.