Design Recipe for Java Methods

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:
  1. What's a meaningful name for the method?
  2. What does it do (from the perspective of someone using it)?
  3. What type of object, if any, does it operate on?
  4. 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?
  5. 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:

  1. If the method needs to be called from outside its class, make it public; otherwise, make it private. (This is an oversimplification, of course....)
  2. If the method doesn't need to operate on an existing object of its class, make it static.
  3. The "type of information it returns" in the contract becomes the return type (or "void" if none).
  4. The method name becomes the method name (surprise!).
  5. For each piece of "information needed", put the type and parameter name inside the parentheses.
  6. 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.
  1. 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.
  2. 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.
  3. 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.

  1. 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.
  2. 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;
                   }
               ...
               }
           
  3. 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,

  1. 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.
  2. 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.
  3. 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!


Last modified: Wed Jan 24 09:11:50 EST 2001
Stephen Bloch / sbloch@adelphi.edu