Chapter 33

Ten Ways to Avoid Bugs

In This Chapter

arrow Adopting a clear and consistent coding style

arrow Keeping track of heap memory

arrow Using exceptions to handle errors

arrow Providing a copy constructor and overloaded assignment operator

It’s an unfortunate fact that you will spend more time searching for and removing bugs than you will spend actually writing your programs in the first place. The suggestions in this section may help you minimize the number of errors you introduce into your programs to make programming a more enjoyable experience.

Enable All Warnings and Error Messages

The syntax of C++ allows for a lot of error-checking. When the compiler encounters a construct that it just can’t decipher, it has no choice but to output a message. It tries to sync back up with the source code (sometimes less than successfully), but it will not generate an executable. This forces the programmer to fix all error messages — she has no choice.

However, when C++ comes across a structure that it can figure out but the structure smells fishy anyway, C++ generates a warning message. Because C++ is pretty sure that it understands what you want, it goes ahead and creates an executable file so you can ignore warnings if you like. In fact, if you really don’t want to be bothered, you can disable warnings.

warning.eps Disabling or otherwise ignoring warnings is an extraordinarily bad idea. It’s a bit like unplugging the “check engine” light on your car’s dashboard because it bothers you. Ignoring the problem doesn’t make it go away. It doesn’t mean that you can always fix the problem but you need to at least understand the warning. What you don’t know will hurt you.

If your compiler has a Syntax Check from Hell mode, enable it.

Adopt a Clear and Consistent Coding Style

Writing your C++ code in a clear and consistent style not only enhances the readability of your program, but also it results in fewer coding mistakes. This somewhat surprising state of affairs results from the fact that our brains have only a limited amount of computing power. When you read code that is clean and neat and that follows a style you’re familiar with, you spend very little brain power parsing the syntax of the C++ statements. This leaves more brain CPU power to decode what the program is trying to do and not how it’s doing it.

A good coding style lets you do the following with ease:

  • Differentiate between class names, object names, and function names
  • Understand what the class, function, or object is used for, based on its name
  • Differentiate preprocessor symbols from C++ symbols (that is, #define 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 format for your module headers that provides information about the functions or classes in each module, the author (presumably that’s you), the date, the version, and something about the modification history.

tip.eps All programmers involved in a single project should use the same coding style. A program written in a patchwork of different coding styles is confusing and looks unprofessional.

Comment the Code While 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. I can understand not taking the time to write voluminous headers and function descriptions until later, but I have never understood why some programmers don’t write short comments while coding.

Have you ever had the experience of asking someone a question, and even as you got to the end of the question, you knew the answer? Somehow formulating the question forced you to organize your thoughts sufficiently so that the answer became clear.

Writing comments is like that. Formulating comments forces you to take stock of what it is you’re trying to do. Short comments are enlightening, both when you read them later and as you’re writing them.

Write comments as if you’re talking to another, knowledgeable programmer. You can assume that the reader understands the basics of the program, so please don’t explain how C++ works. There’s no point in writing comments that explain how a switch statement works unless you’re relying on some obscure point of the language (like the fall-through capability of the switch statement mentioned in Chapter 7).

Single-Step Every Path in the Debugger at Least Once

It may seem like an obvious statement, but I’ll say it anyway: As a programmer, you have to understand what your program is doing. It isn’t sufficient that the program outputs the expected value. You need to understand everything your program is doing. Nothing gives you a better feel for what’s going on under the hood than single-stepping the program, executing it step by step with a good debugger (like the one that comes with Code::Blocks).

Beyond that, as you debug a program, you need raw material to figure out some bizarre behavior that might crop up as the program runs. Nothing gives you that material better than single-stepping through each function as it comes 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 you examine the function by itself rather than after it has been thrown into the pot with the rest of the functions — by then, your attention has gone on to new programming challenges.

Limit the Visibility

Limiting the visibility of class internals to the outside world is a cornerstone of object-oriented programming. The class should be responsible for its internal state — if something gets screwed up in the class, then it’s the class programmer’s fault. The application programmer should worry about solving the problem at hand.

Specifically, limited visibility means that data members should not be accessible outside the class — that is, they should be marked as protected. In addition, member functions that the application software does not need to know about should also be marked protected. Don’t expose any more of the class internals than necessary to get the job done.

A related rule is that public member functions should trust application code as little as possible, even if the class programmer and the application programmer are the same person. The class programmer should act like it’s a fact that the application programmer is a felonious hacker; if your programmer is accessible over the Internet, all too often this assumption is true.

Keep Track of Heap Memory

Losing track of heap memory is the most common source of fatal errors in programs that have been released into the field — and, at the same time, the hardest problem to track down and remove. (Because this class of error is so hard to find and remove, it’s prevalent in programs that you buy.) You may have to run a program for hours before problems start to arise (depending upon how big the memory leak is).

As a general rule, programmers should always 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 MyClass::release() that returns it to the heap. Specifically, MyClass::create() should not require the parent function to release the memory.

If at all possible, MyClass should keep track of such memory pointers on its own and delete them in the destructor.

Certainly, this approach doesn’t avoid all memory problems, but it does reduce their prevalence somewhat.

Zero Out Pointers after Deleting What They Point To

Sort of a corollary to the warning in the preceding section is to make sure that you zero out pointers after they are no longer valid; you do so by assigning them the value nullptr. The reasons for this action become clear with experience: You can continue to use a memory block that has been returned to the heap and not even know it. A program might run fine 99 percent of the time, making it very difficult to find the 1 percent of cases where the block gets reallocated and the program doesn’t work. But why tempt Murphy’s Law?

If you null out pointers that are no longer valid and you attempt to use them to store a value (you can’t store anything at or near the null location), your program will crash immediately. Crashing sounds bad, but it’s not if it exposes a problem. The problem is there; it’s merely a question of whether you find it or not before putting it into production.

It’s like finding a tumor at an early stage in an X-ray. Finding a tumor early when it’s easy to treat is a good thing. Given that the tumor is there either way, not finding it is much worse.

Use Exceptions to Handle Errors

The exception mechanism in C++ (described in Chapter 32) is designed to handle errors conveniently and efficiently. In general, you should throw an error indicator rather than return an error flag. The resulting code is easier to write, read, and maintain. Besides, other programmers have come to expect it, and you wouldn’t want to disappoint them, would you?

Having said that, limit your use of exceptions to true errors. It is not necessary to throw an exception from a function that returns a “didn’t work” indicator if this is a part of everyday life for that function. Consider a function lcd() that returns the least common denominator of its two arguments. That function will not return any values when presented with two mutually prime numbers. This is not an error and should not result in an exception.

Declare Destructors Virtual

Don’t forget to create a destructor for your class if the constructor allocates resources such as heap memory that need to be returned when the object reaches its ultimate demise. This rule is pretty easy to teach. What’s a little harder for students to remember is this: Having created a destructor, don’t forget to declare it virtual.

“But,” you say, “my class doesn’t inherit from anything, and it’s not subclassed by another class.” Yes, but it could become a base class in the future. Unless you have some good reason for not declaring the destructor virtual, then do so when you first create the class. (See Chapter 29 for a detailed discussion of virtual destructors.)

Provide a Copy Constructor and Overloaded Assignment Operator

Here’s another rule to live by: If your class needs a destructor, it almost surely needs a copy constructor and an overloaded assignment operator. If your constructor allocates resources such as heap memory, the default copy constructor and assignment operator will do nothing but create havoc by generating multiple pointers to the same resources. When the destructor for one of these objects is invoked, it will restore the assets. When the destructor for the other copy comes along, it will screw things up.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.133.160