This is one of the first aticles I wrote (and my first cover).  It appeared
in 1989 in Computer Language.  I'm posting it to illustrate my ideas on
different levels of defense in writing bug-free programs.  

--John






Debugging is a large part of programming.  After all, what good
is a program that does not work?  Yet debugging is mostly
overlooked when programming is being taught.  Debugging is a
subject which should be actively studied as part of developing
programming skills.

There are three lines of defenses against bugs.  Debugging is the
removal of bugs that exist.  That is what you are undoubtedly
most familiar with.  There is also antibugging which concerns
methods to prevent bugs from occurring in the first place. 
Between the two is abugging, which is catching bugs before they
can cause harm.

C++ has language elements to aid in all three levels of insuring
program correctness.

Antibugging

The first line of defense is antibugging.  This is the most
difficult to study, since it actually concerns the ability to not
make any mistakes in the first place.  Obviously, good design and
organization is important.

Well, C++ allows one to organize the program very well.  That is
the hype about Object Oriented Design.  By designing classes with
well defined interfaces, you minimize the risk system-wide bugs. 
Any bug will be within some module, and can be dealt with without
messing up the rest of the program.  Keeping the design in small
chunks also minimizes the chance of making mistakes, and makes it
easier to figure out what all a module shout do.

In short, good design equals bug prevention.  Learn how to use
the OOPs principals of C++ for good design.

Constructors and destructors are wonderful.  If you give your
type a constructor, you simply cannot create an object without it
being initialized properly.  If you give the class a destructor,
you can not avoid cleanup.  A goto or return or whatever will
always accomplish the cleanup when the variable leaves scope.

When objects are complicated, initialization can be complicated. 
But the semantics of the constructor take care of making sure
parents and members are all initialized, too.  So there is no
chance of making an error along those lines.

If an object needs to be copied in some special way, such as a
"deep copy" that duplicates all the pointers as well, you can
define an assignment operator that works in place of normal
copying.  You don't have to worry about it after that.  All uses
of the object will follow your rules.

Sometimes you want several different types to be used in a given
place.  Rather than casts and void pointers, you can use
inheritance and conversion operators.  If you use an X where a Y
was expected, the compiler will automatically realize that a Y is
an X, or can be converted into an X.  Rather then making
conversions and casts explicit, build the required information
into the classes.  That way all required conversion and coercion
is automatic.

In short, build as much as you can into the objects.  The class
itself should contain all the intelligence for correct use of the
objects.  Messy things should not even have to be done by the
user, but should be handled internally.  And any special rules
should be made automatic, so the user does not have to worry
about it.

Abugging

Abugging is the ability to catch bugs as they appear.  C++ has a
much stronger ability of over C thanks to its strong type
checking and protection.

If you code a function call wrong, the compiler tells you of the
error.  Naturally, you are grateful the compiler can do this
rather then letting the bug slip through.  You should maximize
this ability by telling the compiler as much as you can about the
program.

Types of variables and parameters should be strong.  Notice that
a typedef is just a synonym for a type, so it does not add any
typechecking (however, future lint utilities might catch things
like this).  An enumerated type, on the other hand, is a unique
type.


typedef int dollars;
typedef int date;

extern void foo (date);
dollars d= 5;

foo (d);   // this error is NOT CAUGHT

----

enum color { red, yellow, blue };
enum sex { male, female };

extern void bar (sex);
color walls= red;

bar (walls);  // this error IS caught

The first example is clearly an error, but the compiler is no
help.  A future lint checker might be, so this is still good
practice.  Also, you can change the representation of date
without messing up the rest of the program.  If you simply used
int throughout, changing it could be a mess.

class date {
   int x;
public:
   int operator= (int y) { return x=y; }
   operator int& () { return x; }
   };

The class above is a way to make a new int type.  Here is a first
cut at making date into a class without having to change any code
that uses it.  Just change the declaration of the date type.  The
class is designed to behave like an int.  However, it will still
not solve the problem, since it behave too much like an int.  It
will still mix with dollars without causing any errors. 
Obviously, the best bet is to make all such types classes, with
the implementation hidden.  Unfortunately, the typedef version
will generate better code then the trivial class on current
compilers.

The const modifier is very useful.  In general, you should use it
wherever you can.  That will prevent you from changing something
accidentally.  Pointers in particular can benefit from the const
modifier.  A pointer to a const cannot change the item it points
to through a dereference.  Also, a const reference will allow you
to pass by reference for efficiency without granting the ability
to alter the original passed object.

Many new C++ programmers overuse the void*.  In C, a void* is a
wild pointer that can point to anything.  In C++, this should be
avoided.  Instead, use a pointer to a family of types (thanks to
inheritence) or conversion operators to make a pointer that
points to just the things it is supposed to.

In antibugging you saw how making use of conversion operators and
inheritance can eliminate the possibility of making errors when
converting or casting.  But this also means that you get an error
if you try something that will not work.  Not only can the
compiler cast or convert for you in legal cases, but will catch
illegal cases at compile time.  So this is another reason to
avoid casts.  Rather, if a conversion or cast is meaningful, tell
the compiler about it and let it be applied automatically.

Many problems are caused by using an object before it is properly
initialized.  C++ allows variables to be defined where needed,
and initialized.  Define your variables at the point of first
use.  This completely prevents this kind of error.  Also,
subsequent editing or code re-arrangement that moves the first
use will cause an error.  If the variable was declared at the top
of the function, you could introduce a use-before-set error and
not catch it.

Debugging

With two barriers against bugs, C++ code is much more reliable
than similar C code.  For example, I once wrote about 400 lines
of C++ that used a great deal of dynamic memory and pointers, yet
the code ran the first time I tried it.  The real work of
manipulating pointers was done in a few well defined places
(antibugging) and any misuse of the high-level datastructures was
caught by the compiler (abugging).

But even with all this going, bugs will creep in.  Then debugging
in the traditional sense is needed.  The good news is that you
will probably have some idea where in the code the bug exists. 
If some object type is malfunctioning, obviously the bug must be
in the code that implements that type.  Thanks to protection, you
can be pretty sure that no code breaks the rules and tinkers with
data it has no business with.  Also, you know it has to be
something that was not caught by the two layers of defenses.

So, how do you find a bug?  First, you need to explore the nature
of the bug.  Run a number of tests, and gather information on how
the program is behaving.  Presumably, you will have a good idea
on the program logic.  By comparing the observed behavior with
the planned behavior, you can figure out what is going wrong. 
Now, you can go to the source at that point and figure out why it
is not doing what you intended.

Most bugs are this trivial to find and fix.  With proper testing,
you can insure your classes are stable before using them in other
code.  But sometimes things go really wrong.

finding it - easy way

Let's say that you figure out where in the program logic the
error lies, but when you look there, it seems fine.  In short,
the function says one thing, but does something else.  When this
happens, the first thing to do is verify that the error is indeed
in this section of source code.  A test of that function can be
created, or you can follow it in the debugger.  This is an
important step.  You can save yourself a wild goose chase, and
now you have hard evidence that the bug is indeed in that
function.

There are several things that can be the cause, but they all stem
from the idea that what the source says to you is not the same as
what it really means.  There are three things to check for,
usually in this order.

The first thing is to make sure that any assumptions made by the
function are valid at the time the function is called.  "What
assumptions?" you may ask.  Well, most code assumes that the
machine is in some state in order to work correctly.  It is good
practice to know what those assumptions are when the function is
being written, so hopefully you will have some comments in the
source telling you what to watch out for.  Look over the code and
discover just what might go wrong if perhaps something is not set
up right before the call.  Also, if this is a member function,
the state of the object being manipulated should be correct.

You might discover a possibility that matches what is really
happening.  Or you might not.  Either way, you can put in
assert() statements or other checks to make sure the assumptions
are valid.  With a debugger, you can simply check the values from
within the running program.  Otherwise, asserts can catch errors
and output statements can tell you about the actual values being
dealt with.

Most of the time, you will discover either (1) some variable is
not set properly, or (2) the function was not designed to handle
this particular case.  In the first case, you need to discover
how that bad value came into being, which is discussed in the
next section.

If the state of the program is correct, but the function simply
does not work, it means that one of the functions it calls is
buggy.  You need to discover which line in the function is
behaving wrong.  This can be done in a way similar to checking
the state.  Find out when things go wrong, and eventually isolate
the line that does not do what it is supposed to.  If you are
using a source level debugger, that is rather easy.

The line says one thing but does something else.  Uaually this
means that the function being called on that line is buggy.  Or
it could mean that the line does not mean what you thought it
did.  Common reasons for that are calling the wrong function,
using a function incorrectly, or getting bit by implicit
conversions or somesuch, or having the code clobbered by a stray
pointer somewhere else in the program.

finding it - hard way

Some problems are not so easy to spot by simply comparing the
observed and expected behavior.  Providing more information can
help.  If the observed behavior is based on the output of the
program only, there can be huge gaps where you don't know what is
going on.  Adding debug statements there is a good idea.  Also,
using a source level debugger can help you by letting you watch
values and program flow.

When you finely do get a symptom, like some value that is set
incorrectly, you need to find where that happens.  If there are
only a few places where that value is used, it might not be too
much work.  But for many cases, it is a real nightmare.

One way is to put in a pair of tests.  If the value is legal at
the first and incorrect at the second, you keep moving them
closer together until you find the problem.

The big problem is that most of the time a bad value is hard to
spot.  It is not that simple, but rather is a problem of the
system being in a bad state.  The test for "bad value" can be
formidable.

So in classes, you can design in debug code to test for the
correct state of the class.  However, it is far easier to apply
abugging principles before the problem occurs than it is to deal
with it as debugging afterwards.  And planning ahead when a class
is being designed can make it easier to spot bad states.

adding debug code

C++ has some interesting features to make is possible to add
debugging tests without drastically altering the program.  As
discussed above, adding a debug statement to a member function
will do a great deal, as all access will go through that member.

Another possibility is to derive a new class and make it call all
the same functions in the old one, but with error checking on the
parameters and consistency checks built it.  Notice that you can
even add new members (both functions and data) to help you.  In
the simplest form, this lets you add debug statements to a class
without modifiying the source for that class.

There are also more interesting ways to watch for things.  You
can add statements to constructors and destructors to monitor the
program.  You can add code to put all objects of a class into a
list, and then periodically run a check on all objects.

If you have problems with pointers, you can create an operator&
that prints out a message as well as returns the address of the
object.

class C {
public:
   C* operator&()
      { cout << "taking the address\n"; return this; }
   };

Likewise, you can create an operator= to watch for assignments,
if there is not one already.  And a copy constructor will watch
for objects being passed as parameters or returned.  There are
many ways of slipping in code that is automatically executed at
certain places, and this can be debug code as well as ordinary
things that need to be done.

HEADINGB the bottom line

You need to understand and use all three levels of defenses
against bugs.  C++ has some nice features to make all three
techniques more useful.  But no language can solve all your
problems.  It still requires care and attention to detail to
prevent bugs, and finding bugs will always be something
programmers will have to do.

New Bugs in C++

C++ has new language features over C, and this means that more
bugs are possible.  More powerful features means more things to
misuse.  No language can ever escape from this.  As you master a
language, you learn the kinds of things that can go wrong.  Here
is a list to get you started on mastering C++.

Virtual Functions

The most infamous new C++ bug is the bad virtual redefinition. 
That is, if foo() is a virtual function and you redefine it in a
derived class intending it to be virtual, but you botch it so it
is not an exact match, and not virtual.  There is no easy way to
detect this, and the only sure way to prevent it is to use your
text editor to cut&paste the definition and never type it
yourself.

Also, if you change the definition of a virtual function, make
sure you change it in all derived classes as well.

The way virtual functions are propagated is something of a design
fault in C++ syntax.  Once a function is defined as virtual then
redefinition is a virtual redefinition, and there is no way to
specify or to tell in each case if a function is virtual or not. 
It is clear how you can accidentally make a function non-virtual. 
But you can have a function made virtual and not know it!

class C : public A, public B {
   // member definitions...
public:
   void foo();
   };

In this definition of class C, foo() is intended to be an
interface function to the class.  However, a parent class B uses
a virtual function foo() for internal use.  So C::foo() is
virtual and you don't know it.  To make matters worse, when B's
members use foo() they get C::foo() which does something
completely different from what it expects!

Scope

The above problem brings us to a more general class of bugs
caused by scopes.  In C++ you have several scope levels-- global,
block, and class.  Throw in inheritance, and you have to be
careful that you are accessing what you intended.

int x;  // a global variable
class C : public A {
   void foo() {  x= 3; };
   };

In the above example, the writer intended to access the global
variable x.  However, he unknowingly inherited a member x from
class A, and he is accessing that instead.  This could do several
things:  It could cause an error that you don't have the right to
access A::x.  It could cause an error that the type is wrong. 
Or, it could take it as a correct statement.

Remember these cases if you get strange errors like that.  In
C++, the scope is checked before the access rights are confirmed. 
So you might have A::x that is private to A and still cause the
error in C::foo() because the member was found, and found to be
private.  Since a member was found in class scope, it did not
check global scope at all.

Ok, so you got that straight.  You make sure you know everything
you are inheriting, not just the parts you are interested in.  In
this case, you went and said ::x=3 to explicitly refer to global
x.  Now here is a variation.  Say your code works fine, and
someone modifies a class definition.  Even if he adds a private
member, unusable to you, it can still alter the meaning of your
code.  Adding a private member would make your code error, but
adding a public or protected member could introduce the above bug
after your code was finished.

With multiple inheritance, scope is worse.  When you create a new
class, the scopes of its parents are merged.  If you inherit the
same member from two or more places, the result is not overloaded
names.  Rather, you get the one closest to you.

Say C is derived from A and B.  In a member function of C, you
call fiddle() which you know is inherited from A.  Actually, it
is not defined in A but in A's great-grandparent.  You don't know
that, you just know that fiddle() is available in A, so it is
inherited by C.  Now, someone adds a function fiddle() to class B
or even to B's parent class.  What happens to your class C?  The
modification of the parents changed the inheritance pattern, and
you are now calling the wrong function.

The moral is that you do need to know a great deal about the
classes you are using.  If you derive a new class, you need to
know all about the parents.  You need to know everything you
inherit.  You can't just keep the good silver and ignore the
ghost in the attic:  you get the entire estate.

The real bad news is that altering the parent classes can have an
effect on derived classes, even if the public interface is not
changed.  Users of a class can know the public interface only,
but derived classes need to know all about the ancestry of its
parents.

Order of Evaluation

One thing C programmers have long ago learned is that the order
of evaluations of subexpressions ad or parameters is undefined. 
So in a case like  printf ("before: %d after %d", x, x+=5);  you
may not get what the statement implies.  However, with overloaded
operators, many programmers quickly forget this.  The C++
statement cout << "before " << x << " after " << x+=5;  suffers
from the same problem.

References are Pointers

C programmers are used to treating pointers with care. 
References overcome many pointer bugs, but some still remain.  In
a case like this:

int& x= d.lookup();
d.insert(5);
x= 2;

You have a bug.  The call to insert() changed the structure of
the database (reallocated memory or moved elements around) and x
no longer references the correct thing.  Whenever you have
references, make sure you understand the lifetime of the
reference.  I use the notation ISB to indicate a function that
returns a pointer or reference to an internal static buffer, or
otherwise gets the answer overwritten with each call.  For a case
like the one above, you should note that the return from lookup()
is only good until the next call to insert() and other functions
that can modify the structure.

