Incremental ADT Development Using Stubs
January 2003
Background
Often, a software developer is called upon to develop an abstract data
type (ADT), or similar reusable software component. In Java such
components
are called classes; other common terms are modules (Modula-2/3),
units (Turbo Pascal) or
packages (Ada). Intermediate
computer science courses therefore often assign projects that require
the
development of such components. In this note we'll discuss a
tried-and-proven
technique called incremental development; we'll use Java syntax
and terminology, but the technique is entirely language-independent.
An ADT class will normally contain some variables and constants --
which
normally are private -- and provide public (and
possibly
some private) methods. These allow a client program
to
instantiate (declare) and initialize objects of the class, and
to
manipulate objects in various ways. Suppose the class provides a way to
build a collection of data -- a table or list of data elements
in
some form. The methods will then allow adding an element to the
collection,
deleting an element from it, finding and modifying an element in
certain
ways, and so forth. Generally, at least one operation is provided to traverse,or
iterate over the structure, "visiting" each element exactly
once,
and doing something with each element's value as it is visited. In the
Java community, such an iterator is often called toString,
and
it builds a single string containing the concatenation of all the
elements
in the collection, in their corect sequence.
Incremental development
In a common type of project assignment, you get a complete specification
or interfacefor a component, in ordinary prose, pseudo-code, or
(sometimes) in actual, compilable form. For this note, let's assume
you're
given a compilable Java class file (".java file") for the
desired
component, and your job is to implement and and test the class. How
should
you go about this?
Many students seem to assume they're required to develop and test
the
entire class all at once. This looks like an overwhelming task, and,
for
beginners, it is! You try to do it; you get overwhelmed; you panic; you
make mistakes; you miss the deadline.
The basic assumption is incorrect. It is not
necessary
to develop the whole class at once! Indeed, it is undesirable to do so,
and professional developers generally do not do so.
It is better -- and usually takes less of your time overall
--
to develop incrementally.To do this, you build, and thoroughly
test,
only one or a few methods at a time. Limiting your work to just a few
key
methods ensures that you really understand those methods. Once that
small
set of methods passes your tests for them, you add an method or two to
the class, then extend your test program to handle the new operations.
If something goes wrong in the tests, it's probably in the methods you
just added. It's also possible that executing the new methods "breaks"
something in the old methods, so you must be careful to keep testing
the
old ones.
You continue to develop in stages, until, eventually, the entire
class
is completed. A nice aspect of this is that if you reach the deadline
with
an incomplete class, at least you can turn in a partial result that works.
There may be missing features, but a partial submission is far better
than
none at all! (This also happens, frequently, in industry!)
Using stubs to facilitate incremental development
Using stubs allows the overall class to be partially tested without
requiring
that all methods be fully coded. A stub is simply a “framework” for a
method.
All we require of a stub is that it be legally compilable and
executable.
Generally this means that a stub must either
-
return its input unchanged or
-
compute some incomplete, but legal, part of its computation.
A stub for a void method can simply do nothing; no executable
statements
are even necessary. A stub for a value-returning method must contain a
valid return statement; if it doesn't, the compiler will reject it. So
include a "fake" return statement that returns a legal but meaningless
value: 0 or MIN_VALUE for an integer, false
for a boolean, " " for a string, etc.
Let's assume you start with a correct and compilable Java source
file
for the class. Here's how to proceed:
-
Compile the file to make sure it will compile. If you get compilation
errors,
correct them immediately.
-
Add the data declarations (these should always always be private),
then compile. Correct any compilation errors
-
Add stubs for all the methods, then compile and correct errors.
-
Develop and test a few methods by replacing the stubs will full method
bodies, then add and test a few at a time, until all operations are
completed.
In your stubs, consider putting a temporary output statement that gives
the name of the stub and the message "under construction" (as we often
see on the web). That way, calling that operation will result in a
predictable
message. When you complete each stub, you can change the message to
indicate
that the stub was called. Once the package is debugged, you can remove
all these extra messages.
Deciding which operations to implement
Start with a minimal set of operations. The details here will
vary
with the type of package, of course. As an example, let's say the
package
provides a "collection" type, as described above. At a minimum, you'll
need to write
-
a method that adds a new element to a collection -- let's call this
method
add
-
a toString method that traverses the collection to build a
long
string and return it to the caller
Start with these two. In your test, first display the result of a toString
activation, to make sure an empty collection is displayed properly.
Then
call add several times; after each add, activate toString
and display its result, to ensure the addition was done correctly (and
also that toString is working properly for all cases). Since
the
unimplemented operations are legally stubbed, your package will always
be compilable.
By the time this is done, you have a pretty good understanding of
how
the operations are supposed to work, so you can start adding operations
incrementally. At each stage, don't add a new operation until
you're
sure the current ones are correct!
This process may look time-consuming, but decades of university and
industry experience shows that using this technique will almost
always
save your time. Try it!
(end of article)