How to Avoid Getting Lost When Writing Programs, or, The Power of Incremental Development

In daily life, we're all constantly making small corrections. Even walking in a straight line is a difficult task when there is no feedback on whether you are, in fact, going straight. If you're making sauce, you'll constantly be checking the consistency of the mixture to make sure it's not too watery. You may find that even when writing, you go back and revise your sentences or restructure paragraphs before you've finished your piece.



In short, in daily life, almost everything we do involves constant feedback on whether we're doing it right. Why would programming be any different? Very few of us, especially when just starting out, will create a program that Just Works. Instead, we'll get a ton of compiler errors, and once we've worked through them, the program will probably not work correctly. If you've written hundreds of lines of code, then you might have dozens or even hundreds of compiler errors to sort through and once you've managed to get through them, you'll still be faced with a frustrating debugging chore because you won't know where in that 200 lines of code your error resides.

Start Small

The key to avoiding this frustration is to start small--unless I have an extremely good idea of what needs to be done, the first thing I do when starting any program is to get a simple program that I can run. This program probably doesn't do much; it might just display the welcome screen. But it does something, and it means that I've got no compiler errors to deal with in that part of the code. Better still, I know it works because I can see it and test it.

What makes a good initial framework? I like to have some basic output and possibly some input. If I'm working on a network application, my first goal is just to send one packet over the network from one computer to the other. Doing these things helps to test your basic assumptions about how things work. If you can't get the basic input to work, then it's unlikely you'll even be able to test the rest of your program.

Build In Increments

Once I've got a simple program running, I try to add small pieces of code that are self-contained enough to be easily tested. This gives me the confidence that my new addition will actually work correctly. Another advantage is that if I have only small additions to my code, then I will limit the number of annoying error messages I have to deal with at one time. I hate dealing with fifty error messages just as much as anyone else, but I'd rather deal with fifty errors in chunks of three or four than all at once!

In many cases, it will be obvious which part of your program you should write next (perhaps because everything else will depend on that piece of code). In some cases, you might have the luxury of choosing between two or more possible next steps. In these cases, I generally like to solve the easiest problem first because I don't want it sitting around bothering me while I try to think about the harder problem. I want to devote all of my mental energy to the hard problem, whereas I can afford to worry a bit about the hard problem while I solve the easier one.

If you're not sure what to write next, don't be afraid to write simpler code than you expect will actually be necessary. It's fine to write some basic functionality without filling in all the details if it's sufficient to let you test an assumption about a part of the code that's more important. Of course, it's not always clear what's most important, but generally it would be whatever is at the core of the program. In a game, it might be the game engine; in an AI program, it might be the learning algorithm. In a network application, it might be the ability to create a network connection. Prioritize the core of the program over features that might never be used!

Finally, once I've added a chunk of code, I test it to make sure that it works as expected. If it doesn't, then at least I know that the mistake has to be in the code that I just added. (There are, of course, times when this isn't the case, but for the most part, it's true.) This means that when I'm debugging, I can ignore most of my old, working code, and focus on looking at what happens when the new code is executing.

Backtracking

One of the great things about building incrementally is that you get feedback on whether you're actually solving the problem. After every small change, you're able to test your implicit assumptions about what should be happening. Sometimes, you might find out that what you expected to happen is very different from what really happened. For instance, you might have incorrect assumptions about how fast your program will be. If it takes an hour to start up, then that's an indication of something wrong! Better to catch it when you first start out than once you've written an entire program based on that assumption.

Better still, even if you don't need to actually back track, you will have a firmer grasp of what you're doing. Once you can actually play with the framework of your program, it's much easier to get a picture for what the rest of it should be doing.

Staying Focused

A major reason for building incrementally is that it lets you focus on a single part of your program at a time. You don't need to worry about whether every line of code will work because you'll have experience with most of it actually working. This lets you focus all of your energy on the new problem you are solving rather than worrying about hundreds of lines of untested code.

Along similar lines, when you're working on a program, it helps to stay focused and work on a single discrete part of the program at a time. If you go back and forth between multiple parts of the code, you'll have to keep more stuff in your head at once (or load and unload information every time you switch what you're working on). You'll also run into the problem of having to come to a stopping place in multiple parts of your code at once before you can test it. If your addition in one place would break some other code, then you have to finish that addition before testing your semi-functional program again.