This document provides general guidelines for writing C/C++ code in a portable fashion for AXIS C++ project. New development must adhere to these guidelines to ensure portability and thus avoid rework. Note that these guidelines are not intended to replace more general guidelines for writing good code, but to supplement and extend them.
These are the general guidelines for engineering and re-engineering code to be portable, and for keeping it that way.
The following guidelines should be followed to maximize portability. The list of guidelines is open for extension - if you find a non-portable construct in any code, raise the issue and it will be addressed.
VC++ and Solaris C++ compilers allow register declarations without a type - they allow a "default" type of int to be inferred. This is not standard C++, and should be avoided.
The Microsoft implementation of localtime is thread safe, while the standard C library (and most Unix) implementation is not. Use localtime_r() as a direct replacement for localtime().
The Microsoft implementation of strtok() is thread-safe, while the Unix implementation is not. The Microsoft version achieves thread-safety by using thread-local-storage, which is relatively inefficient. To create efficient, portable, thread-safe code, use the strtok_r() function instead.
For code that is specific to WIN32, the VC++ compiler defines the WIN32 symbol automatically. So you can use:
#ifdef WIN32
#endif
to isolate this code. For code that is specific to UNIX but is not applicable under WIN32, use
#ifdef UNIX
#endif
VC++ differs from standard C++ in the access granted to nested classes. For example, the following code is legal in VC++, but illegal in C++:
class Outer
{
private:
enum OuterStatus { GOOD, BAD, VERYBAD};
class Inner
{
public:
OuterStatus status;
}
};
The problem is that OuterStatus is not available to class Inner - being a nested class doesn't give Inner rights to private members. One solution is for Inner class to be a friend of the Outer class:
class Outer
{
private:
enum OuterStatus { GOOD, BAD, VERYBAD};
class Inner; // forward decl
friend class Inner;
class Inner
{
public:
OuterStatus status;
}
};
#ifdef __cplusplus
extern "C" {
#endif
....
#ifdef __cplusplus
}
#endif
Functions should be short and sweet, and do just one thing. They should
fit on one or two screenfuls of text (the ISO/ANSI screen size is 80x24,
as we all know), and do one thing and do that well.
The maximum length of a function is inversely proportional to the
complexity and indentation level of that function. So, if you have a
conceptually simple function that is just one long (but simple)
case-statement, where you have to do lots of small things for a lot of
different cases, it's OK to have a longer function.
However, if you have a complex function, and you suspect that a
less-than-gifted first-year high-school student might not even
understand what the function is all about, you should adhere to the
maximum limits all the more closely. Use helper functions with
descriptive names (you can ask the compiler to in-line them if you think
it's performance-critical, and it will probably do a better job of it
that you would have done).
Another measure of the function is the number of local variables. They
shouldn't exceed 5-10, or you're doing something wrong. Re-think the
function, and split it into smaller pieces. A human brain can
generally easily keep track of about 7 different things, anything more
and it gets confused. You know you're brilliant, but maybe you'd like
to understand what you did 2 weeks from now.
C is a Spartan language, and so should your naming be. C programmers
do not use cute names like ThisVariableIsATemporaryCounter. A C
programmer would call that variable "tmp", which is much easier to
write, and not the least more difficult to understand.
HOWEVER, while mixed-case names are frowned upon, descriptive names for
global variables are a must. To call a global function "foo" is a
shooting offense.
GLOBAL variables (to be used only if you _really_ need them) need to
have descriptive names, as do global functions. If you have a function
that counts the number of active users, you should call that
"count_active_users()" or similar, you should _not_ call it "cntusr()".
Encoding the type of a function into the name (so-called Hungarian
notation) is brain damaged - the compiler knows the types anyway and can
check those, and it only confuses the programmer. No wonder MicroSoft
makes buggy programs.
LOCAL variable names should be short, and to the point. If you have
some random integer loop counter, it should probably be called "i".
Calling it "loop_counter" is non-productive, if there is no chance of it
being mis-understood. Similarly, "tmp" can be just about any type of
variable that is used to hold a temporary value.
If you are afraid to mix up your local variable names, you have another
problem, which is called the function-growth-hormone-imbalance syndrome.
See next chapter.
The goal of any software development effort is to produce code that works as intended. The only way to ensure that is to test it. Given that humans learn most efficiently, and perceive patterns best, when stimulus and response are close in time, it follows that the most efficient way to locate and fix defects is to test code as soon as it is written.