- Contract & Purpose
- Decide what information the method needs from the outside world,
and what information it returns to the outside world. Ask the following
questions:
- What's a meaningful name for the method?
- What does it do (from the perspective of someone using it)?
- What type of object, if any, does it operate on?
- In addition to the object it operates on, what other type(s)
of information, if any, does it need in order to do its job?
What would be meaningful names for these additional pieces of
information, to indicate clearly what they're used for?
- What type of information, if any, does it return as a
value?
Write down the answers to these questions in a comment. If you use the
javadoc conventions for your comments, it'll be easier to
produce user-friendly documentation. For example,
/**
Find the square of an integer.
@param num int to square
@return an int equal to num*num
*/
- Header
- Translate your contract into Java syntax.
The pattern is
[modifiers] return_type method_name (type name type name ...)
{
...
}
"Modifiers" means possibly the word static, and
possibly one of the words public, private, or
protected.
Rules of thumb:
- If the method needs to be called from outside its class, make it
public
; otherwise, make it private
. (This is
an oversimplification, of course....)
- If the method doesn't need to operate on an existing object of its
class, make it
static
.
- The "type of information it returns" in the contract becomes the return
type (or "void" if none).
- The method name becomes the method name (surprise!).
- For each piece of "information needed",
put the type and parameter name inside the parentheses.
-
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 above "square" example could be written
down as
/**
Find the square of an integer.
@param num the int to square
@return an int equal to num*num
*/
public static int square (int num)
{
...
}
- 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, you can write your examples in a Java comment,
e.g.
/**
Find the square of an integer.
Examples:
square(0) should give 0.
square(-5) should give 25.
@param num the int to square
@return an int equal to num*num
*/
public static int square (int num)
{
...
}
but it may be more convenient to put your examples in a
static void test() method or a JUnit testing class,
so you can run all your test cases easily, e.g.
/**
Find the square of an integer.
@param num the int to square
@return an int equal to num*num
*/
public static int square (int num) {
...
}
...
/**
Where we put all the standard test cases for the class.
*/
public static void test ()
{
System.out.println ("square(0) = " + square(0) + " (should be 0).");
System.out.println ("square(-5) = " + square(-5) + " (should be 25).");
...
}
- 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 may be 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.
Since in Java, the fundamental compilation unit is the class, you won't
be able to test individual methods until you've written at least the
skeleton of a whole class.
However, you can add one method at a time to
that class skeleton, testing each one before adding the next.
Each time you've finished writing a method and want to test it,
- 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.
- Run the method in question. If you're using a platform like
DrJava or BlueJ, you can invoke it directly, or if you don't want to
type in your test cases every time you test the method, you can put
them all in a static void test method and invoke it
directly, or in a separate JUnit testing class. On some other platforms,
you'll need to write a static void main method whose sole
purpose is to run your test cases.
In either case, if you get
run-time errors, read the error messages carefully, figure out what
they mean, fix them, and compile and run again until there are no
syntax or run-time errors.
- For each of your test cases, observe the actual output. 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 until all test cases produce correct answers.
Remember, whenever you fix a bug, be sure to re-test the previously
working examples to make sure they still work!