thinkapjava 5.1.2 documentation

Program development

«  Input and Output in Java   ::   Contents   ::   Debugging  »

Program development


I present different program development strategies throughout the book, so I wanted to pull them together here. The foundation of all strategies is incremental development, which goes like this:

  1. Start with a working program that does something visible, like printing something.
  2. Add a small number of lines of code at a time, and test the program after every change.
  3. Repeat until the program does what it is supposed to do.

After every change, the program should produce some visible effect that tests the new code. This approach to programming can save a lot of time.

Because you only add a few lines of code at a time, it is easy to find syntax errors. And because each version of the program produces a visible result, you are constantly testing your mental model of how the program works. If your mental model is wrong, you are confronted with the conflict (and have a chance to correct it) before you write a lot of bad code.

The challenge of incremental development is that is it not easy to figure out a path from the starting place to a complete and correct program. To help with that, there are several strategies to choose from:

Encapsulation and generalization:
If you don’t know yet how to divide the computation into methods, start writing code in main, then look for coherent chunks to encapsulate in a method, and generalize them appropriately.
Rapid prototyping:
If you know what method to write, but not how to write it, start with a rough draft that handles the simplest case, then test it with other cases, extending and correcting as you go.
Start by writing simple methods, then assemble them into a solution.
Use pseudocode to design the structure of the computation and identify the methods you’ll need. Then write the methods and replace the pseudocode with real code.

Along the way, you might need some scaffolding. For example, each class should have a toString method that lets you print the state of an object in human-readable form. This method is useful for debugging, but usually not part of a finished program.

Failure modes

If you are spending a lot of time debugging, it is probably because you are using an ineffective development strategy. Here are the failure modes I see most often (and occasionally fall into):

Non-incremental developement:
If you write more than a few lines of code without compiling and testing, you are asking for trouble. One time when I asked a student how the homework was coming along, he said, “Great! I have it all written. Now I just have to debug it.”
Attachment to bad code:
If you write more than a few lines of code without compiling and testing, you may not be able to debug it. Ever. Sometimes the only strategy is (gasp!) to delete the bad code and start over (using an incremental strategy). But beginners are often emotionally attached to their code, even if it doesn’t work. The only way out of this trap is to be ruthless.
Random-walk programming:
I sometimes work with students who seem to be programming at random. They make a change, run the program, get an error, make a change, run the program, etc. The problem is that there is no apparent connection between the outcome of the program and the change. If you get an error message, take the time to read it. More generally, take time to think.
Compiler submission:
Error messages are useful, but they are not always right. For example, if the message says, “Semi-colon expected on line 13,” that means there is a syntax error near line 13. But putting a semi-colon on line 13 is not always the solution. Don’t submit to the will of the compiler.

The next chapter makes more suggestions for effective debugging.

«  Input and Output in Java   ::   Contents   ::   Debugging  »