Coding Style Guide
Introduction
Standardization is important. When projects follow standards, good things happen:
- Different programmers can go into any code, and have a chance to figure out what is going on.
- New people can get up to speed quickly.
- People new to C++/Java are spared from making the same mistakes over and over again.
- People make fewer mistakes in a consistent environment.
Purpose of the Coding Style Guide
The purpose of Coding Style Guide is to describe the standards to be followed in development on WDB. Code must adhere to these standards in order to pass a code review.
Scope
The style guide applies to C++ code.
Naming
Names are the heart of programming. In the past, people believed knowing someone's true name gave them magical power over that person. If you can think up the true name for something, you give yourself and the people coming after power over the code. Don't laugh!
A name is the result of a long deep thought process about context of the entity. Only a programmer who understands the system as a whole can create a name that “fits” with the system. If the name is appropriate everything fits together naturally, relationships are clear, meaning is derivable, and reasoning from common human expectations works as expected. If you find all your names could be Thing and DoIt then you should probably revisit your design.
Philosophy aside, there is a very simple reason to have naming conventions. Having naming conventions saves time; it means that you don't constantly have to look up the source to verify whether MyClass or MyFile are spelled with a large or small 'M'.
Class Names
Name the class after what it is. If you can't think of what it is that is a clue you have not thought through the design well enough. Compound names of over three words are a clue your design may be confusing distinct entities in your system. Revisit your design. Try a CRC card session to see if your objects have more responsibilities than they should.
Avoid the temptation of bringing the name of the class a class derives from into the derived class's name. A class should stand on its own. It doesn't matter what it derives from.
Suffixes are sometimes helpful. For example, if your system uses agents then naming something DownloadAgent
conveys real information.
Class names are written in UpperCamelCase.
Example:
class NameOneTwo; class Name;
No All Upper Case Abbreviations
Write all abbreviations in CamelCase. Why? People seem to have very different intuitions when making names containing abbreviations. It's best to settle on one strategy so the names are absolutely predictable.
Example:
class TypeOfGrib; // NOT TypeOfGRIB class NetworkAbcKey; // NOT NetworkABCKey
Method and Function Names
Methods and Functions are written in lowerCamelCase.
Usually every method and function performs an action, so the name should make clear what it does: checkForErrors() instead of errorCheck(), dumpDataToFile() instead of dataFile(). This will also make functions and data objects more distinguishable.
Classes are often nouns. By making function names verbs and following other naming conventions programs can be read more naturally.
Suffixes are sometimes useful.
* max - to mean the maximum value something can have. * cnt - the current count of a running count variable. * key - key value.
Example:
void retryMax(); // to mean the maximum number of retries void retryCnt(); // to mean the current retry count
Prefixes are often useful:
* is - to ask a question about something. Whenever someone sees Is they will know it's a question. * get - get a value. * set - set a value.
Example:
void isActive();
Class Attribute Names
Class attribute names should be written in lowerCamelCase and suffixed with an underbar ('_').
Example:
class NameOneTwo { public: int varAbc(); int errorNumber(); private: int varAbc_; int errorNumber_; String* pName_; }
Pointers
Pointers should be prepended by a 'p' in most cases. Place the * close to the pointer type not the variable name. The idea is that the difference between a pointer, object, and a reference to an object is important for understanding the code, especially in C++ where →
can be overloaded, and casting and copy semantics are important.
Pointers really are a change of type so the * belongs near the type. One reservation with this policy relates to declaring multiple variables with the same type on the same line. In C++ the pointer modifier only applies to the closest variable, not all of them, which can be very confusing, especially for newbies. You want to have one declaration per line anyway, so you can document each variable.
Example:
String* pName= new String; // Good String* pName, name, address; // Note: only pName is a pointer.
References (Variables and Functions returning references)
References should be prepended with 'r'. This establishes the difference between a method returning a modifiable object and the same method name returning a non-modifiable object.
Example:
class Test { public: void doSomething(StatusInfo& rStatus); StatusInfo& rStatus(); const StatusInfo& status() const; private: StatusInfo& rStatus_; }
Global Constants
Global constants should be avoided. If you still choose to use them, write uppercase letters with '_' separators. You must be careful to not conflict with other global #defines and enum labels.
Example:
const int A_GLOBAL_CONSTANT= 5;
Enum Names
Labels should be all upper case with '_' word separators. This is the standard rule for enum labels.
Example:
enum PinStateType { PIN_OFF, PIN_ON };
Sometimes people use enums as constants (without class scoping). When an enum is not embedded in a class make sure you use some sort of differentiating name before the label so as to prevent name clashes, as done above. If PIN was not prepended a conflict would occur as OFF and ON are probably already defined.
Make a Label for an Error State
It's often useful to be able to say an enum is not in any of its valid states. Always make a label for an uninitialized or error state. Make it the first label if possible.
Example:
enum { STATE_ERR, STATE_OPEN, STATE_RUNNING, STATE_DYING };
C++ File Names and Extensions
In short, use lowerCamelCase for file names. Use the .h
extension for all C/C++ header files, .c
for C source files and .cpp
for C++ source files.
Filenames should be identical with the class names they implement.
Code Documentation
Consider your comments a story describing the system. The header files must be documented using JavaDoc, which will then be extracted by Doxygen (or similar system) and formed into a code documentation.
Make Your Code Discoverable by Browsing
Programmers should be able to navigate your code by looking at markers in the code, namely the names and the directory structure. Nothing is more frustrating to than to have to look at pile of code and have no idea what it's organizing principles are.
Use a logical directory structure. Try to stick to standard names, and otherwise avoid abbreviations. Clear thought is evidenced from the beginning by a directory structure.
Don't put more than one class in a file. Otherwise, how will I know its there when I browse your code? Should I really need to use search to find every last thing? Can't I just poke around the code and find it? I can if you organize your code.
Name your files after your classes. Why would you name a file different than the class? How I am possibly supposed to know what's in the file otherwise?
Write Comments as You Code
You won't every go back later and document your code. You just won't. Don't lie to yourself, the world, and your mother by saying that you will.
So when you do something document it right then and there. When you create a class - document it. When you create a method - document it. And so on. That way when you finish coding you will also be finished documenting.
There's a saying that you don't know something until you teach it. Comments are teaching what you are doing to someone else. When you are writing comments you must generate the thoughts to teach, to explain to someone else the intent behind what you are doing. It's harder to make a coding error when the entire context is hot in your mind.
Make Gotchas Explicit
Explicitly comment variables changed out of the normal control flow or other code likely to break during maintenance. Embedded keywords are used to point out issues and potential problems. Consider a robot will parse your comments looking for keywords, stripping them out, and making a report so people can make a special effort where needed.
Gotcha Keywords
/**
* \todo what needs to be done here
* Means there's more to do here, don't forget.
*
* \bug BUGID what is this bug about
* Means there's a Known bug here, explain it and give the bugzilla bug ID (you did remember to document it in bugzilla, right?).
* For extra points, link the BUGID to the entry in bugzilla.
*
* \attention what to be aware of
* When you've done something ugly say so and explain how you would do it differently next time if you had more time.
*
* \warning Beware of something
* Used to warn users that the following code is very tricky so don't go changing it without thinking.
*
* \note note something here
* A note on the code that you think is important to be in Doxygen.
*/
Gotcha Formatting
Comments may consist of multiple lines, but the first line should be a self-containing, meaningful summary. The writer's name and the date of the remark should be part of the comment. This information is in the source repository, but it can take a quite a while to find out when and by whom it was added. Often gotchas stick around longer than they should. Embedding date information allows other programmer to make this decision. Embedding who information lets us know who to ask.
Example:
/**
* \todo MiA 2007-04-01: Possible performance problem. We should really use a hash table here, but for now we'll stick with a linear search.
*
* \attention MiA 2007-04-01: Check the date
*/
Code Formatting
A common class layout is critical from a code comprehension point of view and for automatically generating documentation. Doxygen will be used to document C++ classes, method, variables, functions, and macros.
Header Files
Please use the following template when creating a new class.
#ifndef XX_h #define XX_h /** * @addtogroup Foo * @{ */ /** * @file * File documentation (if applicable). */ // PROJECT INCLUDES // #include <Goo2.h> // SYSTEM INCLUDES // // FORWARD REFERENCES // /** * A brief description of the class. Futher description. * * Possibly over several paragraphs. * * @see something */ class XX { public: // LIFECYCLE /** Default constructor. */ XX(void); /** Copy constructor. * @param from The value to copy to this object. */ XX(const XX& from); /** Destructor. */ ~XX(void); // OPERATORS /** Assignment operator. * @param from THe value to assign to this object. * @return A reference to this object. */ XX& operator=(XX& from); // OPERATIONS // ACCESS // INQUIRY protected: private: }; /** * @} */ #endif // XX_h
Required Methods Placeholders
The template has placeholders for required methods.
Interface Ordering
Notice that the public interface is placed first in the class, protected next, and private last. The reasons are that programmers should care more about the class's interface than its implementation. When programmers need to use a class they need the interface not the implementation. It makes sense then to have the interface first.
Methods
LIFECYCLE
The life cycle section is for methods that control the life cycle of an object. Typically these methods include constructors, destructors, and state machine methods.
OPERATORS
Place all operators in this section.
OPERATIONS
Place the bulk of a class's non access and inquiry method methods here. A programmer will look here for the meat of a class's interface.
ACCESS
Place attribute accessors here.
INQUIRY
These are the is* methods. Whenever you have a question to ask about an object it can be asked via in Is method. For example: isOpen()
will indicate if the object is open. A good strategy is instead of making a lot of access methods you can turn them around to be questions about the object thus reducing the exposure of internal structure. Without the isOpen()
method we might have had to do: if (STATE_OPEN == State())
which is much uglier.
Header File Guards
Include files should protect against multiple inclusion through the use of macros that “guard” the files.
Implementation Layout
/** * @addtogroup Foo * @{ */ /** * @file * File documentation (if applicable). */ #ifdef HAVE_CONFIG_H #include <config.h> #endif // LOCAL INCLUDE #include "XX.h" // PROJECT INCLUDES // // SYSTEM INCLUDES // //--------------------------------------------------------------------------- // Public Methods //--------------------------------------------------------------------------- // Lifecycle //--------------------------------------------------------------------------- XX::XX() { }// XX XX::XX(const XX&) { }// XX XX::~XX() { } // ~XX // Operators //--------------------------------------------------------------------------- XX& XX::operator=(XX&); { return *this; } // = // Operations //--------------------------------------------------------------------------- // Access //--------------------------------------------------------------------------- // Inquiry //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- // Protected Methods //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- // Private Methods //--------------------------------------------------------------------------- /** * @} */
Required Methods
To be good citizens almost all classes should implement the following methods. If you don't have to define and implement any of the “required” methods they should still be represented in your class definition as comments.
Default Constructor
If your class needs a constructor, make sure to provide one. You need one if during the operation of the class it creates something or does something that needs to be undone when the object dies. This includes creating memory, opening file descriptors, opening transactions etc.
If the default constructor is sufficient add a comment indicating that the compiler-generated version will be used. If your default constructor has one or more optional arguments, add a comment indicating that it still functions as the default constructor.
A default constructor allows an object to be used in an array.
Virtual Destructor
If your class is intended to be derived from by other classes then make the destructor virtual. Virtual destructors ensure objects will be completely destructed regardless of inheritance depth. You don't have to use a virtual destructor when: * You don't expect a class to have descendants. * The overhead of virtualness would be too much. * An object must have a certain data layout and size.
Copy Constructor
If your class is copyable, either define a copy constructor and assignment operator or add a comment indicating that the compiler-generated versions will be used.
If your class objects should not be copied, make the copy constructor and assignment operator private and don't define bodies for them. If you don't know whether the class objects should be copy able, then assume not unless and until the copy operations are needed.
Assignment Operator
If your class is assignable, either define a assignment operator or add a comment indicating that the compiler-generated versions will be used.
If your objects should not be assigned, make the assignment operator private and don't define bodies for them. If you don't know whether the class objects should be assignable, then assume not.
The copy constructor and assignment operator ensure an object is always properly constructed.
The Law of The Big Three
A class with any of (destructor, assignment operator, copy constructor) generally needs all 3. For more information see http://www.parashift.com/c++-faq-lite/coding-standards.html [25.9].
Method Layout
The approach used is to place a comment block before each method that can be extracted by Doxygen and be made part of the class documentation. See the Doxygen documentation for a list of attributes supported by the document generator. Every parameter should be documented. Every return code should be documented. All exceptions should be documented. Use complete sentences when describing attributes. Make sure to think about what other resources developers may need and encode them in with the @see attributes. Example:
/** Assignment operator. * @param val The value to assign to this object. * @exception LibaryException The explanation for the exception. * @return A reference to this object. */ XX& operator=(XX& val);
Methods should have as few parameters as possible. When a method does have a lot of parameters, these should be declared one to a line.
Accessors
Access methods provide access to the physical or logical attributes of an object. Having accessors in the public interface of an object is generally discouraged, as this is usually a symptom of poor object-oriented design. In the event of them being required, however, we will use the “get/set” idiom for accessors. I.e., all accessor functions are prefixed with “get” or “set” as appropriate.
Init Function
Objects with multiple constructors and/or multiple attributes should define a private Init() method to initialize all attributes. If the number of different member variables is small then this idiom may not be a big win and C++'s constructor initialization syntax can/should be used.
Initialize all Variables
You shall always initialize variables. Always. Every time.
Miscellaneous
Indentation
Be Const Correct
C++ provides the const key word to allow passing as parameters objects that cannot change to indicate when a method doesn't modify its object. Using const in all the right places is called “const correctness.”
Use Streams
Not printf, etc. Stdio is not type safe. Stream IO is.
No Magic Numbers
A magic number is a bare naked number used in source code. It's magic because no-one has a clue what it means including the author in 3 months time.