OREWORD by Dave Moore, Director of Development, Microsoft xi
PREFACE xvii
INTRODUCTION xxi
With the growing complexity of software and the associated climb in bug rates, it's becoming increasingly necessary for programmers to produce bug-free code much earlier in the development cycle, before the code is first sent to Testing. The key to writing bug-free code is to become more aware of how bugs come about. Programmers can cultivate this awareness by asking themselves two simple questions about every bug they encounter: "How could I have prevented this bug?" and "How could I have automatically detected this bug?" The guidelines in this book are the results of regularly asking these two questions over a number of years.
1 A HYPOTHETICAL COMPILER 1
If your compiler could detect every bug in your program~no matter the type--and issue an error message, ridding your code of bugs would be simple. Such omniscient compilers don't exist, but by enabling optional compiler warnings, using syntax and portability checkers, and using automated unit tests, you can increase the number of bugs that are detected for you automatically.
2 ASSERT YOURSELF 13
A good development strategy is to maintain two versions of your program: one that you ship and one that you use to debug the code. By using debugging assertion statements, you can detect bugs caused by bad function arguments, accidental use of undefined behavior, mistaken assumptions made by other programmers, and impossible conditions that nevertheless some how show up. Debug-only backup algorithms help verify function results and the algorithms used in functions.
3 FORTIFY YOUR SUBSYSTEMS 45
Assertions wait quietly until bugs show up. Even more powerful are sub system integrity checks that actively validate subsystems and alert you to bugs before the bugs affect the program. The integrity checks for the standard C memory manager can detect dangling pointers, lost memory blocks, and illegal use of memory that has not been initialized or that has already been released. Integrity checks can also be used to eliminate rare behavior, which is responsible for untested scenarios, and to force subsystem bugs to be reproducible so that they can be tracked down and fixed.
4 STEP THROUGH YOUR CODE 75
The best way to find bugs is to step through all new code in a debugger. By stepping through each instruction with your focus on the data flow, you can quickly detect problems in your expressions and algorithms. Keeping the focus on the data, not the instructions, gives you a second, very different, view of the code. Stepping through code takes time, but not nearly as much as most programmers would expect it to.
5 CANDY-MACHINE INTERFACES 87
It's not enough that your functions be bug-free; functions must be easy to use without introducing unexpected bugs. If bug rates are to be reduced, each function needs to have one well-defined purpose, to have explicit single-purpose inputs and outputs, to be readable at the point where it is called, and ideally to never return an error condition. Functions with these attributes are easy to validate using assertions and debug code, and they minimize the amount of error handling code that must be written.
6 RISKY BUSINESS 111
Given the numerous implementation possibilities for a given function, it should come as no surprise that some implementations will be more errorprone than others. The key to writing robust functions is to exchange risky algorithms and language idioms for alternatives that have proven to be comparably efficient yet much safer. At one extreme this can mean using unambiguous data types; at the other it can mean tossing out an entire design simply because it would be difficult, or impossible, to test.
7 TREACHERIES OF THE TRADE 145
Some programming practices are so risky they should never be used. Most such practices are obviously risky, but some seem quite safe, even desirable, because they fill a need without apparent hazard. These treacherous coding practices are the wolves m sheep's clothing. Why shouldn't you reference memory you've just released? Why is it risky to pass data in global or static storage? Why should you avoid parasitic functions? Why it is unwise to rely on every nit-picky detail outlined in the ANSI standard?
8 THE REST IS ATTITUDE 171
A programmer can follow every guideline in this book, but without the proper attitude and a set of good programming habits, writing bug-free code will be much harder than it needs to be. If a programmer believes that a bug can simply "go away," or that fixing bugs "later" won't be harmful to the product, bugs will persist. If a programmer regularly "cleans up" code, allows unnecessary flexibility in functions, welcomes every "free" feature that pops out of a design, or simply "tries" haphazard solutions to problems hoping to hit upon something that works, writing bug-free code will be an uphill battle. Having a good set of habits and attitudes is possibly the most important requirement for consistently writing bug-free code.
EPILOGUE WHERE DO YOU GO FROM HERE? 195
APPENDIX A CODING CHECKLISTS 197
APPENDIX B MEMORY LOGGING ROUTINES 203
APPENDIX C ANSWERS 213
REFERENCES 245
INDEX. 247