I’ve been writing a lot of code over the last 4-5 months, prototyping some new product ideas. Here are some thoughts that I try to keep in mind as I work:
Software development rarely proceeds in a straight line
In fact, progress often looks more like a stock graph with advances and declines. You build a something that seems perfect, with beautiful code, and then as you test it with real-world data you find all sorts of problems. So for a while you fix the bugs and you think, “just a few more bugs and I’ll be done!” Sometimes that’s the case, you fix a few bugs and you’re done. But most times, especially with complicated applications or algorithms, you reach a point where you need to throw away some of that “beautiful code,” and re-think your solution. It’s critical to embrace this destruction during construction. Don’t worry about throwing away code, this isn’t failure! In fact it’s a critical part of the development process that allows you to understand the boundaries of the problem better than you did when you started.
Tests must be developed in parallel with the code
And every condition in the code should have a test. So as you type “if color==red”, you should be thinking: “I need 2 tests, one where color is “red” and one where it’s not”. But you should also be creating those tests while you remember why “red” is a special case. And hey for extra credit could you please put a comment before the “if” that describes why “red” is special! If you build tests in parallel, then when you have to throw away some of your code, you’ll still have something valuable: the test cases that formalize the edge cases you’ve encountered. If you wait even a few days to write tests, some of this “why” will fade away and be lost forever.
Look forward to bug reports
Every bug is an opportunity to make your code better. None of us can sit at our desk and envision every way that our software will be (ab)used. When you get a bug report, some end user has given you insight into the workflow and kind of data that you should expect. This is valuable, learn from it!
Fix the real problem not the symptom
The art of debugging is to find the real problem, not the symptom. Most bugs are the result of a program getting to into an unexpected state. A simple example for C++ is a null pointer dereference. The quick solution is to add a check for null, but the right solution is to figure out why your function got the null in the first place. Take a little extra time diagnosing these bugs, and you might fix the problems of tomorrow, today.
Create a test case before the bug fix
One of the most frustrating things for your users is when a new release breaks something that used to work fine. Or when an old bug resurfaces in a new release. You can prevent many of these regressions if you are diligent in creating tests that capture the problem before you fix it. You need to recreate the problem to fix it anyway. So formalize this bug scenario with a test, before starting the fix.
Tests must be as fast and easy to run as a compile
Too often our testing infrastructure is slow and bulky, and requires us to context switch away form the editor and debugger. This creates a disincentive to test in real-time. It should be as easy and automatic to run tests as it is to compile. The easier you make it to create and run tests, the more testing you’ll do, it’s that simple.
Jeffries, Ron: The Nature of Software Development