An interesting discussion of debugging techniques came up in one of the newsgroups I'm following. People shared many pointers about useful debugging tricks, but some just told about their experience of "bugless" coding. I found it very similar to what I feel.
I rarely do heavy debugging. It's funny, actually, since I do write big and complex pieces of code. Many people around me literally spend days with their noses into the debuggers, untangling complex data structures and following long stack traces. I *never* do that. If I fire up a debugger it's usually to understand how some unknown code flows. How is this ? Am I missing something ?
Then, I saw a reply that lit a big, red, flashing LED (sexier than bulbs...) in my head. A guy shared his experience, which was a paraphrase of my previous paragraph. He summed up the reasons for this as follows:
- Write code in tiny pieces
- Never write anything that cannot be immediately tested
- Design for the most general case possible
- At any stage of development - the accumulated code works. Even if it does little, the little it does is correct!
Bingo !!
This is *exactly* how I write code. If I start to write something, I always start tiny (minimal), compile, and code a driver that can test what I've written. Then, each time I add functionality a small piece after another, compiling and testing each time.
(1) and (4) together make all bugs simple. If you only add a small piece at a time and see it works, it's easier to troubleshoot problems, because there's no need to look very far. (2) is "design for testability", a concept becoming very hot lately (XP, for example...) - when the code is testable, not only that bugs are easy to find, but also changes and refactoring are easy to make - which makes the code good and robust. (3) is important for changes, and there are ALWAYS changes, especially in big projects. If the code is general and flexible, it's easy to adapt and to change. If the code is tailored to specific needs, it's hard to change - which might introduce bugs. Simple.
Coding this way, deep bugs just don't appear (unless they're design bugs, but that's not the ones debuggers are here to fight). When something goes wrong, I know that it worked just 5 LOC ago, so something I added broke it. If I add only a tiny bit at a time, it's trivial to find the problem.
Funny, but this is so deeply engraved in me, that it applies when I do digital design (VHDL) as well. If I code an entity, I *immediately* create a test-bench for it - BEFORE I've added any functionality to the module. Then I fire up Modelsim, compile them together and see it works. Then I start adding functionality, a process a time, checking that it compiles and runs after each step. It's just the way I do it, and I can't do it any other way. If I write (once in a decade) a big part of code w/o compiling and testing it after each tiny bit, I start feeling quite weird.
It's good practice, I guess. I just wish more people would code like this. It's too often to see guys spilling 100s of LOC before even compiling, and then spending a week in the debugger...