I like to try and fix as many of the bugs that I write as early as possible, before they’re deployed to the rest of the team — I hate inconveniencing others with my own defect-laden code. As such, my preference is to do what I can to catch the bugs I write as early as I can.
I rely very heavily on the compiler to catch particular classes of bugs for me, to the point that (unlike my early computer science lecturers) I don’t look at a compile error to be a bug: the compiler is very good at reminding me about parts of the code I’m writing that I haven’t finished changing yet, and does so quickly and with minimal fuss. If I change the assumptions or guarantees of a particular function, deliberately changing the function so that callers will no longer compile is — for some projects — not a terrible way to start tracing through parts of a codebase that might be affected. This can include changing arguments to a different order, more args, fewer args, changing the function name, #if 0/#endif around the function, etc.
There are various scenarios where this may not be so useful — very widely used functions, virtual functions, changes to a library used by a number of disparate programs, etc — but I find that it can be more efficient than grepping through a large number of files, particularly when there’s some ambiguity in the symbol name you’re searching for.
Some other ways to have the compiler error for you include leaving symbols undefined so that the compiler error list becomes something of a todo list, making class members private, removing methods from a base class. There are probably others I use. Many of these changes can be applied as a short-lived change to learn more about potential consequences of a change, and are typically easily revertable (assuming the use of any basic version control system).
Compiler warnings are also well worth considering and, if you operate with warnings-as-errors, impossible to avoid. I’ve found plenty of bugs that would have be horrible to diagnose from their symptoms thanks to the warnings of a good compiler.
Compile-time/static asserts are also a good thing. You can never have too many of those.
After the compile-time stuff, next is linker errors. If it all compiles but won’t link, the undefined symbol list can again become that todo list of functions that you haven’t yet got around to implementing. The downside with link errors is that for a larger program it takes a lot longer to compile and attempt linking the whole thing before you find out about the work yet to be done. There are other tricks that can be done with the linker to deliberately break the build, but that’s beyond what I want to write about right now.
When it all compiles and links, finding the bugs is a matter for the runtime. You need to run the code and find out if it will work or not (for the purpose of this article, I’m going to treat “running a test suite” as “running the code”). Any defects present at this stage typically take more time to identify, and more time to fix — even if it is something blatantly obvious, the edit, compile, link, run cycle can be much longer than just edit, compile.
It’s at this point I get to my intended purpose of this blog post: I want to write fewer bugs. Various classes of bugs are prevented for me by the compiler and linker, but if it’s something that gets all the way to failing at runtime (whatever that means), I’m left disappointed. What to be done about it?
What I’m going to try today and, hopefully, continue to do after today, is keep track of bugs I wrote that survived to the point of being run. I’ll give some thought to how they got there and consider if there was something I could have done (and can do in the future) to prevent that kind of bug. And maybe I’ll find out if I keep making the same kinds mistakes over and over again, or if I can learn to write fewer bugs.
I shall attempt to begin with today’s bugs in another post.