Design Recipes

Design recipe for methods

Contract
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 kind of object, if any, does it operate on?
  2. 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?
  3. 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.
  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, 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.
  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're 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.

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