Celestia Coding Standards
I'm not religious about any particular indentation style or naming conventions for code, but I do believe that it is very important that whatever standards are chosen are used consistently throughout a project. This document describes the coding style already in use throughout the Celestia code base. As Celestia grows, there will undoubtedly be clarifications, additions, and edits made to this document, and I encourage suggestions. However, recommendations that require reformatting or renaming huge chunks of the existing code will be ignored. No stylistic convention is so inherently superior that it's worth all that hassle.

Files

The names of Celestia source files and data files are always lower case. Unix is a case-sensitive OS, but Windows is not; using strictly lower case filenames reduces confusion. The extension .cpp should be used for all C++ sources files. A process is set up to run nightly and cull all .C, .c++, and .cc files from the CVS tree.

You should "guard defines" around include files to prevent multiple inclusion of headers. Take care to assure that the guard macros will be unique. The scheme for naming them in Celestia is _<subdirectory>_<filename>_H_ Here's an example:

// This header file is star.h in the celengine subdirectory.

#ifndef _CELENGINE_STAR_H_
#define _CELENGINE_STAR_H_

class Star
{
...
};

#endif // _CELENGINE_STAR_H_
Since celengine is a library, it is important include the subdirectory in the guard macro name in order to make macro name collisions less likely.



Language
Celestia is unrepentantly a C++ project. C++ has many language features which replace unsafe or awkward constructions in C; use the C++ features wherever possible (which should be everywhere except in some cases where you need to call functions from external libraries.)

STL
Celestia makes heavy use of the Standard Template Library. Resist the urge to write your own dynamic array or tree classes--you can almost certainly use STL's vector or map. Don't do this in a header:

#include <string>

using namespace std;

extern string foo;
Instead, when referring to an STL class or function in a header, prefix it with std. Implementation modules shouldn't be forced to import the entire std namespace just because they include a particular header. The right way:
#include <string>

extern std::string foo;

Strings
Use C++'s string class rather than C-style zero terminated strings. The string class is safer, more convenient, and in many cases, more efficient than C strings. You can always call string::c_str() for a pointer to a zero terminated string to pass to external API functions that require one, but it's important to keep in mind that c_str() does not allocate a new string. Methods which are declared to accept C++ string parameters can also accept C strings, because the string class's string(char*) constructor is automatically applied.

void foo(const string& s)
{
...
}

void bar()
{
    string text("Test");

    // This will work properly:
    foo(text);

    // . . . and so will this:
    foo("Test");
}

Casts
Type casting should be avoided as much as possible. However, it is still occasionally necessary to use it, particularly when calling external API functions. When you do have to cast, favor C++ style casts (reinterpret_cast, static_cast, and const_cast) over C casts. While they're a pain to type, they're much easier to notice when browsing the code (since casts are the source of so many bugs, it's important that they're as obvious as possible.)

UINT CALLBACK DialogProc(HWND hDlg, UINT message,
                         WPARAM wParam, LPARAM lParam)
{
   HWND hwnd;

   // Bad cast
   hwnd = (HWND) lParam;

   // Better cast
   hwnd = reinterpret_cast<HWND>(lParam);
}

You do not need to bother with C++ style casts when casting between numeric types.

Const
Use const liberally. Properly const'd programs are safer. Const declarations act as documentation for developers and hints for the compiler to help it produce more efficient code. All reference parameters to a function should be declared const unless they are intended to be modified by the function. Make a point of declaring const any method which does not modify class members. When iterating through the members of a container, use const_iterator instead of iterator unless you intend to modify the elements of the container.

Reference Parameters
Favor passing parameters as references to passing them as pointers. If it is not expected that the value of the parameter will ever be NULL, you should certainly use a reference instead of a pointer. The current Celestia code isn't as consistent about pointers vs. references as it should be.

I/O
While C++ I/O is usually preferred, C style I/O with printf/scanf is also also acceptable. In many cases, printf is simply more convenient and produces much more readable code than C++'s operator overloading based I/O. The gets function from the C standard library should never, ever be used. It's unsafe, and gets related buffer overflows have been the source of many security holes.


Indentation and Spacing

Naming
Portability
Celestia is a cross-platform project, and this requires working around the quirks of several different compilers. Here are a few to watch out for.

The Scope of for
The ANSI C++ standard states that the scope of a variable declared in the initialization part of a for statement is just the for loop. Microsoft Visual C 6.0 disagrees and lets the definition "leak" out of the loop. Other compilers (like the GNU C++ compiler) conform to the C++ standard. Avoid writing code that relies on either behavior. The simplest rule is to never declare variables in the initialization spection of a for loop. If you do declare a variable there, make sure that it is not referenced outside of the for loop.

// This won't compile with Visual C++ (because it's broken)
int sum = 0;
for (int i = 0; i < 5; i++)
    sum += i;
for (int i = 0; i < 5; i++)
    sum += i * i;

// This version won't compile with g++ (because it conforms to the standard)
int sum = 0;
for (int i = 0; i < 5; i++)
    sum += i;
for (i = 0; i < 5; i++)
    sum += i * i;

// This version works on any compiler
int sum = 0;
int i;
for (i = 0; i < 5; i++)
    sum += i;
for (i = 0; i < 5; i++)
    sum += i * i;