Back to the WFC main page
Win32 Foundation Classes Coding Guidelines
$Revision: 8 $
Software Development In General
Never, Never, Never Believe What Microsoft Says
Microsoft is chasing a whole bunch of rabbits at the same time.
They don't deliberately set out to screw you up but they just
can't seem to help it.
Always, Always, Always Believe What Microsoft Does
- We have no choice.
- Microsoft usually announces APIs/technology way too soon.
It may sound like a good idea at the time but they will turn
on a dime to meet a new goal leaving you holding the bag
(and missing deadlines).
Never Completely Design a Program Before You Start Coding
- I call this "rocket style" development. You aim it,
fire it and hope it hits the target. Tiny little miscalculations
at launch will result in the rocket being off by miles.
Use an iterative approach
instead (kinda like a cruise missle). Design a little, code a
little, deliver a little, over and over again. Believe it or not,
this results in better components and a product that the users
need. Only the simplest problems in the world lend themselves
to pre-designing. Give your customers frequent, tangible, working
results. When customers see what your perception of reality is
they can correct you (like a cruise missile making course corrections
when it drifts off course).
- Create your object model then implement that model but don't try
to create a one-to-one mapping of your model to source code. Such detail
in an object model hides the intentions of the model. A relationship
between objects may result in several methods when coded in C++. Why
cloud the model with these details? Keep the model and the implementation
of that model (in whatever programming language you wish) separate. I use
Peter Coad's object methodology. The
thing I like most about his method is it allows me to communicate
with my customers in a language the customer understands.
- Software development, despite all the snake-oil salesmen and
over-educated OOPsters, is nothing more than yet another application of
The Scientific Method.
Remember when you were in the fifth grade? If not, here's a synopsis of the
Scientific Method:
- Observe
- Define
- Hypothesize
- Experiment
- Interpret Results
- Publish Findings
- Go to step 1.
Seems so simple doesn't it? Software development is just another type
of problem to be solved. Here's how the steps map to the software
development process:
- Observe
- Someone thinks we need software to solve a problem.
- Define
- Gather the requirements. Create your scenarios that
describe the specific problems you are going to solve.
- Hypothesize
- Develop the object model. Brainstorm about ways
to solve the defined problems. Go on a vision quest (i.e. get a clue).
- Experiment
- Implement the object model to see if it really works.
It probably won't but that's why we implement them.
- Interpret Results
- When the model breaks at implementation time,
figure out why. Is it a paradigm problem? Is it details that were left
out of the model? Are the right requirements defined?
- Publish Findings
- This is communication, plain and simple. If you
don't publish your results, other programmers
can't reuse the software you've written. The Web is a great way to do this.
Lessons learned in this project can be used in others.
- Go to step 1
- Iterate! Go through the process over
and over again until you get it right.
Code Ain't Art
- Code should be easy to understand. If a programmer writes
code that you can't understand, fire them. You'll save money in
the long run. Source code is a method of communicating ideas
from one human to another.
- You should not have to be at the same level of technical expertise
as the original author. Part of being a good programmer is the ability
to express complex situations in terms anyone can understand. Sticking
with the art theme, you shouldn't have to be painter to recogize the
Mona Lisa.
The more you know about art (i.e. more technically astute) the deeper your
appreciation of artwork.
- Never fully employ all features of a programming language.
Use only enough fo the power of the language to get your job done.
One of the dumbest things you can do as a programmer is to
discover a new feature of a language and immediately use it in
production code. "Advanced" features of a language are
usually less portable between compilers. For example, C++ templates can
work on one compiler just fine but break on another. By using these
"advanced" features of a language, you raise the
sophistication level of the maintainer (or reuser) of the code.
This means you must hire senior programmers in order to fix your bugs.
Reuse is thrown right out the window. Just because you know how to
call C++ destructors and constructors directly to avoid a memory
allocation doesn't meant the next programmer will.
Choose To Live
- Never use structured exception handling unless you are forced to.
Structured exception handling is for lazy programmers and leads to
rockets blowing up shortly after launch (witness the recent Ariane 5
destruction because Ada threw an exception that no one caught).
If something bad happens,
make a note of it and return an "an-error-happened"
code to the caller.
- Use exception handling. Seems like a contradiction doesn't it?
Exception handling is a wonderful thing. Structured is a
demon from hell waiting to snatch your soul. The difference between
the two is who handles the exception. In structured exception
handling, you throw an exception and hope someone (in your call stack)
handles it. The problem with this approach is someone invariably doesn't
handle it and your software dies. Plain old exception handling handles
the exception at the lowest level and communicates to the caller that
something bad happened (usually by setting an errno-type variable
and returning FALSE).
- Every function fails, expect it.
Rarely Abbreviate
- Source code should read like a book. Your manager should be
able to pick it up and understand what it is doing. Yes, that
means you have to spell things out. Programmers that complain
about "all that typing" are short sighted. The time
it takes to type in source code is but a blink of an eye compared
with the life cycle of a program. The time you "saved"
by not typing all those characters is costing you dearly when
you have to go back and modify the code. I actually had a programmer
tell me with a straight face that the length of his variable names
was inversely proportional to the scope of the variable. In lay
terms, if a variable is used all over the place then it will have
a short name, if it is used in only one place then it will have a
long name.
Trust No One
- Never have a "Debug build" and a "Retail build."
Developing this way leads to products that are susceptible to
"random" errors. Do parameter checking on all public
functions. If a function is going to be reused then it has to
be bullet proof. If you're given a pointer, check it for validity
before dereferencing it. Never put error checking in only
one build of a project.
C++ Coding Conventions
Don't overload operators
One of the neat things about C++ is the ability to redefine what
operators mean. Like most power, it corrupts those who use it.
When you must define operators for your classes,
keep it simple.
For example, the assignment operator (=).
Remember, source code is supposed to communicate ideas from one
human to the next. If you redefine lots of operators then the next
guy to come along and use your code will not know what is going on.
Also, don't put any logic into operator implementations.
Have them call existing member methods. For
example, the assignment operator (=) should call Copy().
This keeps functionality in one place instead of many. You
can overload this one function in a child class and not have
to worry about all the other entry points being covered.
In General
What: Don't use friend.
Why: This stems from my object-purist attitude. Friends break the rules
of object oriented design. This means your code will be susceptable to
legal bugs (i.e. you are following the rules of the language which leads
to undesired results). Friend functions allow you to circumvent the
protections built into the class.
What: Don't use initializers in constructors.
Why: When using intializers, you never know when
they are going to get called. Things get undefined when you
start calling functions. The problem is the C++ specification
doesn't make it clear exactly when initializers fire. All we
know is that they happen before the body of the constructor
is executed. Once you reach the body of the constructor, all
constructors have been called. This behavior is defined. When
given a choice, I'll go with defined behavior every time. This
becomes a real problem when you switch compilers. Since the
initializer behavior is undefined, nothing a compiler chooses
to do is wrong. You can get very different results from different
compilers which leads to debugging parties.
Example:
// Bad Example
class A
{
private:
int m_X;
public:
A( int i ): m_X( i ) {};
int GetX( void ) const { return( m_X ); }
void SetX( int x ) { m_X = x; }
};
class B : public A
{
public:
B() : A( GetY() ) {};
int GetY( void ) const { return( GetX() + 1 ); }
};
// Good Example
class A
{
private:
int m_X;
public:
A( int i ){ m_X = i; }
int GetX( void ) const { return( m_X ); }
void SetX( int x ) { m_X = x; }
};
class B : public A
{
public:
B(){ SetX( GetY() ); }
int GetY( void ) const { return( GetX() + 1 ); }
};
What: Initialize variables when they are declared. When memory is freed, set
the pointer to NULL.
Why: Having variables in a known state greatly reduces the
chance for bugs. Invalidating pointers after freeing the memory they
point to greatly eases the identification of problems. There's nothing
trickier than tracking down an errant pointer. If the pointer is set to NULL
and then dereference, your program will blow up. This means it is easy
to track downa and squash the bug.
Example:
// Bad Example
void some_function( int x_coordinate )
{
char * buffer;
if ( x_coordinate > 100 )
{
buffer = new char[ 1024 ];
}
else if ( x_coordinate < 100 )
{
buffer = new char[ 513 ];
}
if ( buffer != NULL )
{
delete buffer; // If x_coordinate was equal to 100, BLAMO!
}
}
// Good Example
void some_function( int x_coordinate )
{
char * buffer = NULL;
if ( x_coordinate > 100 )
{
buffer = new char[ 1024 ];
}
else if ( x_coordinate < 100 )
{
buffer = new char[ 513 ];
}
if ( buffer != NULL )
{
delete buffer;
}
}
What: Never rely on defaults.
Why: Defaults change. Never
assume void as an argument list or return
type for a function. Never
assume class members are private.
Example:
// Bad Example
class string
{
int m_Length;
public:
string();
int GetLength();
};
// Good Example
class CString
{
private:
int m_Length;
public:
CString();
int GetLength( void );
};
What: Do one job in one place.
Why: This is one of my pet peeves. Implement functionality in one place
then have all other entry points to this functionality call the one
that implements it. Copying of objects is a good example. There are
three times when you want to copy the contents of one object into
another:
- Copy Constructor
- Copy Method
- Assignment operator
Instead of implementing the copy in three places, do it in one and
have the others call it. You should have an = operator for every
class that has a copy constructor.
Example:
// Bad Example
class CMyValue
{
protected:
int m_Value;
public:
CMyValue() { m_Value = 0; };
CMyValue( const CMyValue& source ) { m_Value = source.m_Value; };
~CMyValue() { m_Value = 0; };
void Copy( const CMyValue& source ) { m_Value = source.m_Value; };
void SetValue( int new_value ) { m_Value = new_value; };
CMyValue& operator=( const CMyValue& source ) { m_Value = source.Value; };
};
// Good Example
class CMyValue
{
protected:
int m_Value;
public:
CMyValue() { SetValue( 0 ); };
CMyValue( const CMyValue& source ) { Copy( source ); };
~CMyValue() { SetValue( 0 ); };
void Copy( const CMyValue& source ) { SetValue( source.m_Value ); };
void SetValue( int new_value ) { m_Value = new_value; };
CMyValue& operator=( const CMyValue& source ) { Copy( source ); };
};
Naming Conventions
- In General
What: Never abbreviate. Always spell out the entire word in variable/function names.
Why: Source code is meant to be read by people. Most programmers think source code
is for compilers. That is wrong. Source code is a medium by which humans communicate with
compilers to produce executables. Compilers don't understand the ideas being communicated
in source code, humans do. Therefore, source code should require as few brain cells as
possible to communicate those ideas from human to human. Humans should not waste time
trying to decipher another human's abbreviations.
Example:
// Bad Example
struct _me
{
int ec;
int cbec;
};
// Good Example
struct _MemoryError
{
int error_code;
int number_of_bytes_that_caused_the_error;
};
- Class Names
What: Class names should begin with a capital C and have capital letters
separating words.
Why: This follows Microsoft's convention (one of the few I agree with).
It will let the reader know they are looking at a class (as opposed to a structure).
Example:
// Bad Example
class string { void copy(); };
// Good Example
class CString
{
public:
void Copy( void );
};
- Variable Names
- Class Members
What: Public member methods and variables should have capital separating words.
Private or protected should be prepended with the "m_" wart.
Why: It immediately notifies the reader that there's something different
about this variable. The "m_
" wart lets the reader know they are dealing
with a special variable/function. It reduces confusion when trying to review code.
Example:
// Bad Example
class string{
protected:
getbuffer();
public:
allocatebuffer();
};
// Good example
class CString
{
protected:
void m_GetBuffer( void );
public:
void AllocateBuffer( void );
};
- Local Variables
What: Local variables will consist of all lower case letters, words separated
by underscores.
Why: A slightly different coding style will let the programmer know which
variables belong to a function and which ones belong to a class.
Example:
// Class
class CString
{
public:
int BufferSize;
int GetBufferPages( void ) const;
};
//Bad Example
int CString::GetBufferPages( void ) const
{
int BufferPages = BufferSize / 4096;
return( BufferPages );
}
// Good Example
int CString::GetBufferPages( void ) const
{
int number_of_buffer_pages = BufferSize / 4096;
return( number_of_buffer_pages );
}
- Global Variables
What: Global variables should be avoided. But when they can't their names
will consist of mixed case names with capital letters separating words. They will
also be preceeded with the "g_" wart.
Why: The "g_" let's the reader know there's something special
about this variable. Global variables will cause you no end of heartache
when writing multithreaded code.
Example:
// Bad Example
int ReferenceCount;
// Good Example
int g_ReferenceCount;
- Static Variables
What: Static variables will have the first word of their variable
name be "static" and capitalized appropriately.
Why: Statics can get you in trouble. There's a valid need for
them but we want the reader to always be cognizant of the static property
of the variable. Static variables will cause you no end of heartache
when writing multithreaded code.
Example:
// Bad Example
class Zipity
{
protected:
static int m_Dodah;
};
void dump( void )
{
static int number_of_times_i_have_been_called;
}
// Good Example
class Zipity
{
protected:
static int m_StaticDodah;
};
void dump( void )
{
static int static_number_of_times_i_have_been_called;
}
- A Summary Of Warts
Warts are those little things on variable names that aren't words.
Hungarian notation (that Microsoft uses) is the most hideous form of
warts known. In WFC, I've tried to minimize the confusion a wart can cause.
The goal of my warting style is to give the reader a little more
information than the words provide. In novels, authors can always
separate their sentences with "He said" or "She said"
to let you know who is talking. As software authors, we are at the mercy
of the programming language.
Coding Considerations
- Choose to live
If you have a choice between life and death, choose life.
Never assume that a pointer is valid. If you are passed a pointer as a parameter,
you should use exception handling to catch invalid pointers. Don't forget
that arrays are pointers too. Always assume the value of a pointer parameter
is the result of a rand() function call.
class Dumper
{
protected:
DWORD m_ErrorCode;
public:
BOOL Dump( CSomeObject * object_p );
};
// Bad Example
BOOL CDumper::Dump( CSomeObject * object_p )
{
object_p->Dump( afxDump );
return( TRUE );
}
// Good Example
BOOL CDumper::Dump( CSomeObject * object_p )
{
// We were passed a pointer, don't trust it
try
{
object_p->Dump( object_p );
return( TRUE );
}
catch( ... ) // ... means catch all exceptions
{
m_ErrorCode = EXCEPTION_WAS_THROWN;
return( FALSE );
}
}
Return to Sam's Home Page