Almost twenty years ago, I got a lot of insight into this from David Thielen's excellent book "No Bugs: Delivering Error-Free Code in C and C++", which is now available as a free PDF.
He taught me two great ideas...
Bugs don't come from nowhere. All of us programmers sit down and write them into our code with our own fingers.
"Bug" connotes that some outside agency decided to infest your program with bugs and that if you live a clean life, and sacrifice small furry animals at the foot of your computer, they will go away... This concept is important because it colors your approach to debugging your code. If you view mistakes as "bugs," you hope none are found. (You hope the good fairy came by, sprinkled pixie dust, and the bugs left.)
Bugs should not be called bugs, they should be called Massive Fuck-Ups [MFUs]... MFUs exist because programs are written by people, and people make mistakes... You will write MFUs. You will sit down and with complete malice of forethought put MFUs in your code. Think about it - you know that you are the one putting the bugs in there. So if you sit down to code, you will be inserting some bugs.
Since it is all programmers' inescapable destiny to write bugs, I need to code defensively, including things that will jump up, scream, and wave red flags when they detect a bug.
Having been written in the early 90s, the specifics on this in Thielen's book are rather dated. For instance, on Linux and Mac OS X, you no longer need to write your own wrapper for the C++ new operator; you can use valgrind for that.
But there are a few things I routinely do for C/C++/ObjC:
- When I reasonably can, turn on the compiler's "Warnings are errors" option, and fix them all. (I maintain one legacy project where fixing those all at once would take weeks, so I just fix a file every few weeks - and in a few years, I can flip that option on.)
- Use a static code analysis tool, like Gimpel's PC-Lint or the very nifty one now built into Apple's Xcode. Coverity is even better, but the cost is for large corporations, not mere mortals.
- Use dynamic analysis tools, like valgrind, to check for memory problems, leaks, etc.
- As Thielen says (and the chapter is still worth reading): Assert The World. Of course, nobody but an idiot will call your function with a nil pointer - and that means somebody, somewhere, is an idiot who will do just that. It might even be you in three years when what you were doing today has gotten foggy. So just add an assert at the beginning of the function to validate that pointer argument - it takes three seconds to type, and goes away in the release executable.
- In C++, RTTI is your friend. Again, nobody but an idiot will call your function with a pointer to the wrong kind of object - which means that, inevitably, some idiot will - and the cost to defend against that is negligible. In C-based code derived from GObject, you can do the same thing with the defensive dynamic cast macros.
- Automated unit and regression tests are now a key part of my repertoire. On one project, they are an integral part of the release build system, and the build won't complete unless all of them pass.
- Another key part is logging code in both the debug and release executables that can be enabled at runtime by something like an environment variable.
- Write defensive tests so programmers running debug executables can't ignore them if they fail. Runtime messages to the console can be ignored. The program crashing with an assert cannot be ignored.
- When designing, provide public APIs, and private implementations that outside code can't get at. That way, if you have to refactor, nobody's relying on some magic interior state variable or something. In C++ classes, I'm a big fan of protected and private for this. I also think proxy classes are great, but don't really use them myself.
Of course, what you'll do for a new language or technology will vary in the details. But once you take into your heart the notions that bugs are Massive Fuck-Ups You Wrote With Your Own Fingers, and your code is under constant assault from an army of idiots, with you at the head as the general, I'm sure you'll figure out suitable defensive techniques.