- Contract
- Decide what information the method needs from the outside world,
and what information it returns to the outside world. Ask the following
questions:
- What kind of object, if any, does it operate on?
- In addition to the object it operates on, what other kind(s) of
information, if any, does it need in order to do its job?
- What kind of information, if any, does it return as a
"value"?
I usually write the answers in a Java comment in a standard format: for
example,
// ShishKebab.hasOnions : nothing => boolean
would indicate a method operating on a ShishKebab, requiring no other
information, and returning a boolean value.
- Examples
- Write down one or more examples of how you would use this method,
along with the "right answer" you expect it to produce.
This step is useful in at least three ways.
- It allows you to try
out the syntax people will use to invoke the method, and perhaps learn
that your first guess at the syntax is inconvenient to use, so you
should change the syntax before wasting a lot of time implementing it.
- It allows you to discover, early on, that your contract wasn't
quite right, and that you actually want different kinds of information
going in or out. If that happens, change the contract now and
re-write your examples accordingly; that will waste less time than going
on and having to change the contract later.
- it gives you a ready source of test cases, already in legal Java
syntax, thus removing one possible excuse for skipping the
Testing step, below.
Start with the simplest possible examples, then work up to more and more
complicated examples. Again, write your examples in a Java comment,
e.g.
// new Skewer().hasOnions() => false
// new Onion(new Skewer()).hasOnions() => true
// new Lamb(new Skewer()).hasOnions() => false
// new Lamb(new Onion(new Skewer())).hasOnions() => true
// new Onion(new Lamb(new Skewer())).hasOnions() => true
// new Lamb(new Tomato(new Skewer())).hasOnions() => false
- Header
- Translate your contract into Java syntax. I always write both the
starting and the ending braces of the method definition at this point,
because I know that if I don't, I'll forget and leave one of them out.
For example, the contract
// ShishKebab.hasOnions : nothing => boolean
would translate into the Java code
class ShishKebab {
...
boolean hasOnions () {
...
}
...
}
- Template
- You actually have somewhat more information at this point than what will
fit into the header, and you can use this to fill in some of the body.
- For example, if your method is supposed to return
a String, you can be pretty sure that the last
statement in its body will be
return something-of-type-String;
You just need to figure out what the something-of-type-String
is, and you're done with the body.
- If you're working with an abstract class that has several concrete
subclasses, it is highly likely (though not certain) that the abstract
class will have an abstract method declaration:
abstract class Shish {
...
public abstract boolean countOnions();
...
}
and each of the concrete subclasses will have a concrete implementation
of that method, e.g.
class Skewer extends Shish {
...
public boolean hasOnions() {
...
return something-of-type-boolean;
}
...
}
- If you're writing a method for a class that contains instance
variables, you're likely to need to use some of those instance variables.
If those instance variables are instances of classes, you are likely to
need to send them messages. In particular, if those instance variables
are instances of the same class that you're defining (or its
superclass), you are likely to need to send them the same
message you are currently defining, e.g.
class Tomato extends Shish {
Shish previous;
...
public boolean hasOnions () {
...
something about previous.hasOnions()
...
return something-of-type-boolean;
}
...
}
- Body
- Fill in the stuff in between the braces in the method definition.
This is the part that takes the most thinking, but
we've tried to avoid "blank-page syndrome"
by writing the contract, examples, header, and template first.
For example, in the above Tomato
example, once you've got the template in place, the only remaining
challenge is how to use the
answer to previous.hasOnions()
to get the answer to
the question you're trying to solve, so you can return
it as something-of-type-boolean.
- Testing
- Testing is essential if you want to have any faith that your
programs work. If you can't be bothered to test your programs, I can't
be bothered to grade them.
- Write a "testbed" main program whose only purpose is
to try each of the examples in turn and print out the results so you
can see whether they're correct. Again, start from the simplest
examples on your list and move on to the more complicated ones.
After you've tried all the examples you wrote down before, try some
even more complicated examples for which, even if you don't know the
right answer now, you can at least tell whether the answer the
computer produces is "reasonable".
- Compile your program. If it has syntax errors, read the error
messages carefully, figure out what they mean, fix them, and compile
again until there are no syntax errors.
- Once there are no syntax errors, run your program. If it
produces run-time errors, read the error messages carefully, figure
out what they mean, fix them, and compile again until there are no
syntax or run-time errors.
- For each of the examples in your "testbed" program,
observe what the program actually does. If it's not what you
expected, either you were expecting the wrong thing (possible,
though unlikely) or there's a bug in your program. Figure out
how the answer is wrong (not just that it's wrong)
and how this wrong answer could have happened, fix it, and compile
and run again. When you fix a bug, be sure to re-examine the
previously working examples to make sure they still work!
Last modified:
Tue Nov 10 16:30:54 EST 1998
Stephen Bloch / sbloch@boethius.adelphi.edu