Avoiding Bugs in Your C++ Program
Don’t start debugging your code until you remove or at least understand all the warnings generated during compilation. Enabling all the warning messages if you then ignore them does you no good. If you don’t understand the warning, look it up. What you don’t know will hurt you.
Adopt a clear and consistent coding style
Coding in a clear and consistent style not only enhances the readability of the program but also results in fewer coding mistakes. Remember, the less brain power you have to spend deciphering C++ syntax, the more you have left over for thinking about the logic of the program at hand. A good coding style enables you to do the following with ease:
- Differentiate class names, object names, and function names
- Know something about the object based on its name
- Differentiate preprocessor symbols from C++ symbols (that is, #defined objects should stand out)
- Identify blocks of C++ code at the same level (this is the result of consistent indentation)
In addition, you need to establish a standard module header that provides information about the functions or classes in the module, the author (presumably, that’s you), the date, the version of the compiler you’re using, and a modification history.
Finally, all programmers involved in a single project should use the same style. Trying to decipher a program with a patchwork of different coding styles is confusing.
Comment your code as you write it
You can avoid errors if you comment your code while you write it rather than wait until everything works and then go back and add comments. Not taking the time to write voluminous headers and function descriptions until later is understandable, but you always have time to add short comments while writing the code.
Short comments should be enlightening. If they’re not, they aren’t worth much and you should be doing something else instead. You need all the enlightenment you can get while you’re trying to make your program work. When you look at a piece of code you wrote a few days ago, comments that are short, descriptive, and to the point can make a dramatic contribution to helping you figure out exactly what it was you were trying to do.
In addition, consistent code indentation and naming conventions make the code easier to understand. It’s all very nice when the code is easy to read after you’re finished with it, but it’s just as important that the code be easy to read while you’re writing it. That’s when you need the help.
Single-step every path at least once
As a programmer, it’s important for you to understand what your program is doing. Nothing gives you a better feel for what’s going on under the hood than single-stepping the program with a good debugger. (The debuggers included in the IDE of interactive compilers work just fine.)
Beyond that, as you write a program, you sometimes need raw material to figure out some bizarre behavior. Nothing gives you that material better than single-stepping new functions as they come into service.
Finally, when a function is finished and ready to be added to the program, every logical path needs to be traveled at least once. Bugs are much easier to find when the function is examined by itself rather than after it’s been thrown into the pot with the rest of the functions — and your attention has gone on to new programming challenges.
Avoid overloading operators
Other than using the two stream I/O operators operator<<() and operator>>() and the assignment operator operator=(), you should probably hold off overloading operators until you feel comfortable with C++. Although a good set of overloaded operators can increase the utility and readability of a new class, overloading operators (other than the three just listed) is almost never necessary and can add significantly to your debugging woes as a new programmer. You can get the same effect by defining and using the proper public member functions instead.
After you’ve been C-Plus-Plussing for a few months, feel free to return and start overloading operators to your heart’s content.
Balance heap handling
Generally, programmers should allocate and release heap memory at the same “level.” If a member function MyClass::create() allocates a block of heap memory and returns it to the caller, then there should be a member function MyClass::release() that returns the memory to the heap. Specifically, MyClass::create() should not require the parent function to release the memory itself. This certainly doesn’t avoid all memory problems — the parent function may forget to call MyClass::release() — but it does reduce the possibility somewhat.
Use exceptions to handle errors
The exception mechanism in C++ is designed to handle errors conveniently and efficiently. Now that this feature has been standardized, you should use it. The resulting code is easier to write, easier to read, and easier to maintain. Besides, other programmers have come to expect it — you wouldn’t want to disappoint them, would you?
Avoid multiple inheritance
Multiple inheritance, like operator overloading, adds another level of complexity that you don’t need to deal with when you’re just starting out. Fortunately, most real-world relationships can be described with single inheritance.
Feel free to use multiple-inherited classes from commercial libraries, such as the Microsoft MFC classes. Microsoft has spent a considerable amount of time setting up its classes, and it knows what it’s doing.
After you feel comfortable with your level of understanding of C++, experiment with setting up some multiple inheritance hierarchies. That way, you’ll be ready if you need it.