This document describes a tool that was designed to help students learn the structured programming method. The tool checks the correctness of simple programs constructed according to this method. It is intended to provide feedback to students by detecting errors in their programming and reasoning. The tool verifies programs in a compositional way by checking each subproblem independently. It is argued that this tool is better suited for pedagogical purposes than other verification tools as it enforces the structured approach and provides informative feedback on errors. The document presents an example program for finding the most frequent value in an array and how the tool can detect errors in the program's statements or specifications.
1. A Tool For Helping Teach A Programming Method
Isabelle Dony
Université catholique de Louvain
Place Sainte-Barbe 2
B-1348 Louvain-la-Neuve
dony@info.ucl.ac.be
Baudouin Le Charlier
Université catholique de Louvain
Place Sainte-Barbe 2
B-1348 Louvain-la-Neuve
blc@info.ucl.ac.be
ABSTRACT
We present and discuss a tool that checks the correctness
of simple programs constructed according to the structured
programming method. The tool is intended to provide in-
teresting feedback to students learning the programming
method: it detects programming and/or reasoning errors
and it provides typical counter-examples. We argue that
our system is better adapted to our pedagogical context than
other verification tools and we report on preliminary exper-
iments with the tool in a third year programming course.
Categories and Subject Descriptors
D.2.4 [Software]: Software Engineering—Software/Program
Verification; F.3 [Theory of computation]: Logic and
Meaning of Programs
General Terms
Algorithms, Documentation, Experimentation, Verification
Keywords
Formal specification, Invariant, Program construction, Pro-
gram verification, Programming errors, Programming course
1. INTRODUCTION
In this paper, we present a tool we have specifically de-
signed to help our students to understand the structured
programming method developed by Dijkstra and others (see
e.g., [4, 7, 8, 11]). Our motivation to build this tool is
twofold. On the one hand, existing verification tools (e.g.,
[14, 3, 6, 1]) are too complex to be used in a pedagogi-
cal context ; moreover they often lack completeness (and,
sometimes, even soundness [6]). On the other hand, teach-
ing “formal” (i.e., rigorous) program construction with pen
and paper does not motivate students at all. Thus, since
students love to use tools, providing them with a tool that
checks not only their programs but also their specifications
and the structure of their reasoning seemed appealing to us.
Permission to make digital or hard copies of all or part of this work for
personal or classroom use is granted without fee provided that copies are
not made or distributed for profit or commercial advantage and that copies
bear this notice and the full citation on the first page. To copy otherwise, to
republish, to post on servers or to redistribute to lists, requires prior specific
permission and/or a fee.
ITiCSE’06, June 26–28, 2006, Bologna, Italy.
Copyright 2006 ACM 1-59593-055-8/06/0006 ...$5.00.
Obviously, building such a system is far from an easy task.
It may even be thought completely unfeasible to experts in
the field. Our approach is to restrict our ambition to a very
simple programming language with simple types (limited to
finite domains) and arrays, and to address (relatively) small
examples such as searching and sorting algorithms. In this
context, it is possible to specify problems and subproblems,
both clearly and formally, using a specific assertion language
based on mathematical logic.
The rest of the paper is organised as follows. In Section 2,
we recall the main lines of the structured method of Dijkstra
and others, and we explain why teaching this method is
difficult; we also consider the use of existing verifying tools
in our context. In Section 3, we present our tool based on
a typical example. In Section 4, we report on experiments
of using the tool with students in a programming course.
Section 5 contains the conclusion.
2. A PROGRAMMING METHOD
2.1 Description of the method
The programming method we teach is based on specifi-
cations, invariants, and decomposition into subproblems, as
advocated by Dijkstra, Gries, and Hoare, just to mention a
few famous computer scientists [13, 10, 7].
The steps to construct an iterative algorithm are the fol-
lowing: we first define the set of variables of the program,
then we formally specify the problem by means of a precon-
dition (Pre) and a postcondition (Post), then we choose a
invariant (Inv), and finally, we derive the statements: the
initialisation (Init), the iteration (Iter), the closing (Clos),
and the halting condition (H) so that the following proposi-
tions are true:
• { Pre } Init { Inv }
• { Inv and not H } Iter { Inv }
• { Inv and H } Clos { Post }
We use Hoare’s notation [13], i.e., { P } S { Q }, to mean
that if the assertion P holds, then it is guaranteed that
• S terminates and does not generate any run-time error
• after executing S, the assertion Q also holds.
(Notice that we use Hoare’s notation to express total cor-
rectness, not only partial correctness as in Hoare’s original
proposal [13].) If a problem is too complex to be solved by a
single loop, it can be decomposed into several subproblems,
which must be specified and constructed independently. Fi-
nally, to prove the termination of a loop, a variant (i.e.,
a positive integer function of the program variables, which
strictly decreases) must be provided.
2. 2.2 Teaching the method: some issues
The programming method is taught in two programming
courses at two different formality levels. In the first (in-
troductory) course [15], it is used very informally to help
construct (simple) programs more systematically. In the
second (more advanced) course, we expect the students to
get a deeper understanding of the method.
The benefit of the method stems from the divide-and-
conquer approach: problems are broken into subproblems
until only very simple problems are obtained. Moreover,
the construction of a loop is also broken into independent
subproblems through the use of an invariant and a variant.
However, such an approach can be successful only if students
are able to write specifications (and invariants) that are both
sound and complete. If the method is not supported by
a formal specification language, it is very hard to explain
to the students what a good specification (or invariant) is.
This is partly due to the lack of mathematical maturity of
the students, and partly due to the fact that students tend
to prefer a global approach of “understanding” a complex
algorithm as a whole, in an operational way, without any
attempt to make a declarative reasoning.
Thus, we concluded that a helping tool should enforce
the use of the method: decomposition into subproblems and
complete specification of the subproblems. Moreover the
tool should provide an informative feedback to the students
when they make specification or reasoning errors.
2.3 Usability of existing verification tools
Nowadays a number of verification tools exist but they
have not been designed to be used in a genuine pedagogical
context. We have tried to use them in our context to solve
some simple problems according to the structured program-
ming method.
The Extended Static Checker ESC [6] uses a theorem prover
to check the correctness of classes annotated with JML spec-
ifications [5]; it has been used to check the absence of errors
such as null pointer dereferencing, array bound violation,
and division by zero. In our context, ESC is not precise
enough: it reports many false warnings and may report cor-
rectness when there are bugs. Besides, the success of a verifi-
cation may heavily depend on the way an assertion is written
(noted by trying to verify the binary search algorithm). Sim-
ilar problems have been found with an algorithm computing
the next permutation (see [9]). Additionally, JML is not ex-
pressive enough to give a complete formal specification for
most algorithms (e.g., the next permutation algorithm).
The Blast tool [1, 12] focuses on checking sequential C
code, using well-engineered predicate abstraction and ab-
straction refinement tools. The assertion language is re-
stricted to the C syntax and, up to now, Blast does not han-
dle arrays precisely. We tried to use it for algorithms that do
not use arrays like Indian exponentiation, simple integer di-
vision, and squaring using the relation (x+1)2
= x2
+2x+1.
Problems appear probably because the theorem prover used
by Blast does not handle full arithmetic.
The theorem prover PVS [3] can be used to formally prove
program correctness. The disadvantage of PVS is that, even
when the teacher has provided the axioms and deductions
rules, the verification is not yet fully automated. Hence, the
system is not really usable in our pedagogical context.
SPARK (a subset of Ada) and its tools have been used in
an academic context [14]. Since this system uses a theorem
Data:
const n <= 5 ; const minv = 97 ; const maxv = 99 ;
tab a : array [1 .. n] of minv .. maxv ;
Auxiliary_variables:
var g : 0 .. maxint ; var d : 0.. maxint ;
var w : 0 .. maxint ; var nw : 0.. maxint ;
var nvG : 0 .. maxint ; var nvD : 0.. maxint ;
Result_variables:
var v: minv..maxv ; var nv: 0..maxint ;
Precondition:
(exist i : 1 <= i <= n :
(forall j : 1 <= j <= i - 1 : a[j] >=a [j + 1])
& (forall k : i <= k <= n - 1 : a[k] <=a [k + 1]))
Postcondition: unchanged(1, n : a)
& nv = (# k : 1<= k <= n : a[k] = v)
& (forall w : minv <= w <= maxv :
nv >= (# k : 1<= k <= n : a[k] = w))
Invariant: unchanged(1, n : a)
& 1 <= g & g <= d & d <= n + 1
&& nv = (# k : 1 <= k <= g - 1 : a[k] = v)
+ (# k : d <= k <= n : a[k] = v)
& (forall w : minv <= w <= maxv :
nv >= (# k : 1 <= k <= g - 1 : a[k] = w)
+ (# k : d <= k <= n : a[k] = w))
& (forall k : g <= k <= d - 1 :
(forall i : 1<= i<= g - 1 : a[k] < a[i])
& (forall i : d<= i<= n : a[i] > a[k]))
Init: g := 1 ; d := n + 1 ; nv := 0 ; v :=0
Iter: if (a[g] > a[d - 1]) then w := a[g]
else w := a[d - 1] end ;
nw := 0 ;
sp(spRight.in) ; nw := nw + nvD ;
sp(spLeft.in) ; nw := nw + nvG ;
if (nw > nv) then v := w ; nv := nw else skip end
Clot: skip
Halting_condition: g = d
Variant: d - g
Figure 1: The main algorithm for the most frequent
value in a valley
prover, only very simple exercises can be handled since the
students are not able to guide the theorem prover [14].
Let us make it clear again: our goal is not to make experts
in theorem provers; this could be the goal of other, still more
advanced, courses on formal program verification and vali-
dation. What we want is a friendly tool, able to enforce the
use of the structured programming method and to pinpoint
specification and reasoning errors in typical programming
problems.
In the next section, we present our specialised tool.
3. DESCRIPTION OF OUR TOOL
3.1 An example
We illustrate our tool with an algorithm that finds the
most frequent value in an array in a (so-called) valley form.
We define a “valley” as any sequence of integer values that
can be decomposed in two sequences, the first of which is
decreasing and the second is increasing. As an example, the
sequence 5, 4, 4, 3, 3, 4, 6, 6 is a valley and the number 4 is its
most frequent value.
First, let us have a look at a correct version of the al-
gorithm, together with its specifications and loop invariant,
exactly as it must be given to our system (see Figure 1).
The formal specification can give us an idea of the ex-
pressivity of the assertion language supported by the sys-
3. tem. Expressivity is a main concern, because in a learn-
ing context, specifications and invariants must express their
meaning as directly as possible. The precondition expresses
formally that the array is a valley. The postcondition says
that v is the most frequent value in the array a and that nv
is the number of occurrences of v in the array a. Contrary
to the specification language, the programming language is
very simple, as seen in the statements. The algorithm uses
two subproblems not detailed here, which are saved in files
spLeft.in and spRight.in, respectively. Their specifications
are the following:
spLeft
Precondition:
initialised(1,n:a) & initialised(w)
& 1 <= g & g <= d & d <= n + 1
Postcondition:
unchanged(w) & unchanged(d)
& unchanged(1 , n : a) & g_0 <= g & g <= d
&& (forall j : g_0 <= j <= g - 1 : a[j] = w)
& nvG = g - g_0
& (g = d || a[g] != w)
spRight
Precondition:
initialised(1,n:a) & initialised(w)
& 1 <= g & g <= d & d <= n + 1
Postcondition:
unchanged(w) & unchanged(g)
& unchanged(1,n:a) & g <= d & d <= d_0
&& (forall j: d <= j <= d_0 - 1 : a[j]= w)
& nvD = d_0-d
& (d=g || a[d-1] != w)
The goal of spLeft is to compute, in the subarray a[g..d-1],
the number of contiguous occurrences of w starting at index
g, and to move g just after the last occurrence of w. The
goal of spRight is symmetrical.
3.1.1 Checking correctness
To verify the program correctness, the tool works in a
compositional way. Every subproblem is verified indepen-
dently of the others. For instance, checking the main prob-
lem does not require the code of the subproblems, but it
relies on their specification only. Similarly, for each sub-
problem, each block of statements is verified independently
of the others, according to the specification and the invari-
ant.
Notice that all variables range over a finite domain. The
verification time depends on (and grows very quickly with)
the size of the domains. Nevertheless, for most problems,
it is sufficient to define a small domain to find counter-
examples or to get a good clue of the correctness. Notice
also that constants such as an array size can also be specified
as ranging over a finite domain, allowing the verification of
many borderline cases at once.
Our system is able to prove the correctness of the main
algorithm (with the data domains fixed in Picture 1) in 3
seconds1
. The subproblems spLeft and spRight are verified
in about half a second.
We now illustrate how our system deals with programming
and specification errors, and with inconsistencies between
the program and its assertions.
1
we use a Toshiba Satellite 5000-204 laptop, 1.10 GHz Intel
Pentium III processor with 512 Mb of RAM on Windows
XP SP2
Figure 2: A counter-example displayed during the
scenario 3.1.2
3.1.2 Finding errors in statements
Let us consider a scenario where, according to the same
specifications and invariant, the student has to write (dis-
cover) the statements. Suppose he writes the code of Picture
1, but does not pay attention to the fact that the two sub-
problems can cross in the array: he specifies the subproblems
in such a way that g can go beyond d (but not beyond n +
1). Moreover, d can decrease down to 0, regardless of the
value of g. Hence, the postcondition of spLeft becomes:
unchanged(w) & unchanged(1,n:a) & g_0 <= g & g <= n+1
&& (forall j:g_0<=j<= g-1: a[j]= w)
& nvG = g-g_0 & (g=n+1 ||a[g]!= w)
For n = 5, the system gives an explicit counter-example
in 61ms:
Counter_example: verification of {Inv and not(H)} Iter {Inv}:
Inv and not H : a=[97,97,97,97,98],d=5,g=1,n=5,nv=1,v=98
Inv : a=[97,97,97,97,98],d=1,g=5,n=5,nv=8,
nvD=4,nvG=4,nw=8,v=97,w=97
As we can see in Figure 2, the counter-example is pre-
sented in a readable grid. Inside the text area containing
the algorithm (see Figure 3), the violated part of the in-
variant, i.e., g<=d, is underlined, as well as the sequence
of statements implied (the calls to the two subproblems).
Here, the student can easily notice that the values v have
been counted twice.
3.1.3 Detecting a too weak invariant
Another possible scenario is to let the student choose an
invariant. We easily imagine that he correctly manages to
split the array in three parts, attributes correctly the roles of
v and nv, but that he forgets to say that the middle part of
the array, i.e., the part not yet considered in the computing,
has values smaller than the two parts on the sides.
The following counter-example is obtained in 50 ms:
Counter_example: verification of {Inv and not(H)} Iter {Inv}:
Inv and not H : a=[97,97,97,97,97],d=2,g=1,n=5,nv=4,v=97
Inv : a=[97,97,97,97,97],d=1,g=1,n=5,nv=4,
nvD=1,nvG=0,nw=1,v=97,w=97
Observing the values of the variables at the state Inv and
not H, the student can easily feel that something is missing
in the invariant.
4. Figure 3: Visualisation of the tool during the scenario 3.1.2
3.1.4 Alerting out-of-bounds in assertion and run-
time errors
To write the invariant of this problem, the student may
use another mathematical sentence to express that the part
not yet considered in the computing has values smaller than
in the two parts on the sides. According to the precondition
saying that the array is a valley, the student may write
a[g] < a[g-1] & a[d-1] < a[d] & a[g-1] < a[d] & a[d-1] < [g]
without paying attention to the out-of-bound exceptions.
Again, the system will give him an alert by giving explicit
values for g or d, and by underlining the involved expression.
Also, if the user writes, in the iteration, the condition a[g]
> a[d], the system will demonstrate that there is an out-of-
bound exception with d = 6 and n = 5.
3.2 A note on the implementation
Our system is implemented (see [9]) by means of finite
domain constraint programming techniques as provided by
the multiparadigm programming language Oz [17, 2]. This
language allows us to dynamically create constraints that
are logically equivalent to the problem to be verified: inter-
leaving constraint generation and constraint solving, we can
specify an adequate distribution strategy to solve the con-
straints and to provide counter-examples. Moreover, using
Oz, it is quite easy to instrument constraints with additional
data structures allowing us to analyse violated assertions
and wrong statements involved in counter-examples, includ-
ing run-time errors and badly defined assertions. Com-
pleteness and soundness of our implementation stems from
the fact that we exhaustively solve the constraints (unless
counter-examples are produced and the user decides to give
up their generation). Of course, this is possible only because
we make the variables range over finite (almost always very
small) domains. We believe that this limitation is not harm-
ful, in our context.
4. EXPERIMENTATION IN A PROGRAM-
MING COURSE
4.1 Using the system with students
The tool has been used in the first part of the course
“Méthodes de Conception de programme” [16]. During four
weeks, exercises with increasing difficulties were given to the
students. And every week, they could hand out some of their
solutions and their self-evaluation (which involves the use of
the tool).
The first lab session was a first approach to the tool.
The teacher illustrated the tool with examples fully speci-
fied and coded. Students had the possibility to change some
statements or assertions and could understand the counter-
examples. The second lab session involved exercises com-
pletely specified (with a given invariant), but without any
code. The students were asked to write the code based on
these specifications. The third lab session included exercises
specified (without any given invariant). The students had
to choose an invariant and then to fill in the statements. Fi-
nally, during the fourth session, they had to construct more
complex algorithms requiring a decomposition into subprob-
lems. During the third and fourth sessions, specifications
were sometimes given informally.
Three or four problems had to be solved at each session.
5. The list of problems included the following ones : various
kinds of sequential and binary searches, Indian exponentia-
tion, binary division, Dijkstra’s Dutch national flag, Gries’s
plateau problem, finding how many times a value occurs in
a sorted array in logarithmic time (which involves two sym-
metric binary searches).
4.2 Evaluation
First, we have noted a good motivation among the stu-
dents: the number of exercises solved by students was way
over our expectations and very much greater than in the
previous years, when students had to solve exercises with
pen and paper.
Another positive experience is that the tool itself high-
lighted the difficulties of the students directly to themselves.
The iterative process of such a tool seems also very interest-
ing, as it directly gives a feedback to the students and mo-
tivates them to improve their program (and specifications),
and try it again, while the classical approach involves only
one evaluation by the teacher.
Nonetheless, a risk of introducing the tool might be that
it can be used with an approach similar to the process of
“fixing” bugs in programs by trial and error. Therefore, it
is still necessary that the teacher elaborates on the differ-
ent nature of specifications and programs and on the way
the programming method can be adapted to more complex
(“real life”) problems.
The students will be given another course on compiler con-
struction where they have to construct subtle and complex
parsing algorithms. Such algorithms can be designed with
the same programming method but their specifications and
invariants are not practically expressible in any tool that
we know of. Thus, we will get further feedback on whether
students have really gained new practical skills with the pro-
gramming method.
5. CONCLUSION
We have presented a new tool specifically designed to sup-
port the structured programming method applied to simple
problems. The tool has been used by students in a pro-
gramming course where the objective is to get a deep un-
derstanding of the structured programming method and of
its practical applicability. These experiments show that stu-
dents are much better motivated by the use of the tool than
by solving problems with pen and paper. Further investi-
gations are needed to conclude whether or not they really
have gained new skills for solving more complex problems
with the same method.
6. ACKNOWLEDGMENTS
We thank Philippe Delsarte who provided a careful proof-
reading of the final version of this paper.
7. REFERENCES
[1] Blast. http://www.eecs.berkeley.edu/ blast.
[2] The mozart programming system.
http://www.mozart-oz.org.
[3] The pvs specification and verification system.
http://pvs.csl.sri.com.
[4] R. C. Backhouse. Program Construction and
Verification. Prentice-Hall, 1986.
[5] L. Burdy, Y. Cheon, D. Cok, M. Ernst, J. Kiniry,
G. T. Leavens, K. R. M. Leino, and E. Poll. An
overview of jml tools and applications. International
Journal on Software Tools for Technology Transfer, 7,
2005.
[6] D. Detlefs, K. Rustan, M. Leino, G. Nelson, and
J. Saxe. Extended static checking. Technical Report
Research Report 159, Compaq Systems Research
Center, 1998.
[7] E. Dijkstra. A Discipline of Programming.
Prentice-Hall, Englewood Cliffs, 1976.
[8] E. Dijkstra and W. Feijen. A Method of Programming.
Addison-Wesley, 1988.
[9] I. Dony and B. Le Charlier. A program verification
system based on oz. In Proc. second International
Conference MOZ 2004, volume 3389, Charleroi,
Belgium, 2004. LNCS.Springer.
[10] R. Floyd. Assigning meanings to programs. In Proc. of
Symposia in Applied Mathematics, volume 19, pages
19–32. Mathematical Society, 1967.
[11] D. Gries. The Science of Programming.
Springer-Verlag, 1981.
[12] T. Henzinger, R. Jhala, R. Majumbar, and G. Sutre.
Lazy abstraction. In In proc. of the 29th Annual
Symposium on Principles of Programming Language,
pages 58–70. ACM Press, 2002.
[13] C. Hoare. An axiomatic definition of semantics.
Communications of the ACM, 12(10), 1969.
[14] K.-K. Lau. A beginner’s course on reasoning about
imperative programs. In Teaching Formal Methods
Proc. CologNET/FME Symposium TFM 2004, volume
3294, Ghent, Belgium, 2004. LNCS.Springer.
[15] B. Le Charlier. Introduction à l’algorithmique et à la
programmation.
http://www.icampus.info.ucl.ac.be/SINF1150/.
[16] B. Le Charlier. Méthodes de conception de
programmes.
http://www.icampus.info.ucl.ac.be/INGI2122/.
[17] P. Van Roy and S. Haridi. Concepts, Techniques, and
Models of Computer Programming with Practical
Applications in Distributed Computing and Intelligent
Agents. The MIT Press, 2004.
View publication stats
View publication stats