c++ code design rules and guidelines
UNDER CONSTRUCTION
Alexandre R.J. François
Adapted from: Lakos, 1996,
ch. 2, Meyers, 1997, and
other sources
Last modified: 04 October 2010
The beauty of any fine art comes not only from creativity but
also from discipline.
(Lakos, 1996, p. 63)
C++ allows for much creativity, which can result in poor quality
performance in terms of usability, maintainability and performance.
This document lists some rules and guidelines that, if followed, help
improve code quality.
The most important rule: keep things as clear and simple as possible!
declaration vs. definition
A declaration introduces a name into a program
A definition provides a unique description of an entity (e.g. type, instance, function) within a program
internal vs. external linkage
A name has internal linkage if it is local to its translation unit and cannot collide with an identical name defined in another translation unit at link time
A name has external linkage if, in a multi-file program, that name can interact with other translation units at link time
logical vs. physical design
Logical design addresses only architectural issues
Physical design addresses organizational issues
component
A component is the smallest unit of physical design
In C++, one header file and one implementation file
A component is the appropriate fundamental unit of design
- A component bundles a manageable amount of cohesive functionality that often spans several logical entities (e.g. classes and free operators) into a single physical unit
- A component captures an entire abstraction as a single entity, and also allows for consideration of physical issues not adddressed by class-level design
- An appropriately designed component (being a physical entity unlike a class) can be lifted from one system and reused in another system without having to rewrite any code
The logical interface of a component is that which is programmatically accessible or detectable by a client
The physical interface of a component is everything in its header file
naming conventions
- Be consistent about identifier names
- Use uppercase letters to delimit words in multi-word names
(alternative is underscores)
- Use a consistent method to distinguish type names from non-type names
- Non-type (data and function) names begin with lowercase letter; type (entities that are neither data nor function) names start with an uppercase letter
- Use a consistent method to distinguish class names
- First letter capitalized, prefixed with capital C
- Use a consistent method to highlight class data members
- prefix with
m_
- Use a consistent method to highlight data types in variable names
n: integer, f or d: floating point, b: boolean, str: string, p for pointers, etc.
- Use a consistent method to identify immutable values
such as enumerators, constant data, and preprocessor constants
- All uppercase with underscores
- Be consistent about names used in the same way
- Adopt consistent method names and operators for recurring design patterns such as iteration.
In C++, often i and j are integer indexes, p is a pointer; etc.
- Use a consistent method to highlight global variables
- prefix with
g_
the global name space
- Partition the global namespace
- Use
namespace
Global data
- Avoid data with external linkage at file scope
- No global variables
Free functions
- Avoid free functions (except operator functions) at file scope in header files
-
- Avoid free functions with external linkage (including operator functions) in implementation files
-
Enumerations, typedefs, and constant data
- Avoid enumerations, typedefs, and constants at file scope in header files
-
Prepreocessor macros
- Avoid using preprocessor macros in header files except as include guards
- Prefer
const and inline to #define
Include guards
- Place a unique and predictable (internal) include guard around the contents of each header file
-
- Place a redundant (external) include guard around each preprocessor include directive in every header file
- Optional: important for very large projects
Names in header files
- Only classes, structures, unions, and free operator functions should be declared at file scope in header files
-
- Only classes, structures, unions, and inline (member or free operator) functions should be defined at file scope in header files
-
layout
- Prefer space saving layouts
- e.g. place opening bracket on same line as statement
- Prefer C++ style comments
-
- Organize class members by categories of funcionality
- e.g. creators (bring objects into and out of existence), manipulators (non-
const member functions), accessors (const member functions)
Sometimes grouping member functions into other categories can
facilitate understanding of the basic functionality of an unfamiliar
class.
- Place implementation details (private and protected) ahead of public interface
-
documentation guidelines
- Document the interfaces so they are usable by others
-
- Have at least one other developer review each interface
-
- Explicitly state conditions under which behavior is undefined
-
- Document assumptions
- Use of
assert statements can help
documentation style guide
adapted from A
Style Guide section in
Sun's How
to Write Doc Comments for the Javadoc Tool
- When mentioned in descriptions, keywords and names should appear
with the <code> style
- Keywords, namespaces, class names, method names, field names, argument names,
code examples, etc. should be placed between
<code>-</code> tags.
- Do not over-use in-line links.
-
- Do not use parens in the general form of methods and constructors
- The
getVertices method ensures that the object
returned is allocated rather
than The getVertices() method ensures...
- Use 3rd person (descriptive) rather than 2nd person (prescriptive).
- Gets this fractal flake's subdivision level rather
than Get this fractal flake's subdivision level
- Method descriptions begin with a verb phrase:
- Gets this fractal flake's subdivision level rather
than This method gets this fractal flake's subdivision level
- Class/interface/field descriptions can omit the subject and simply
state the object:
- A vector of vertices rather than This field is a vector of vertices
- Use "this" instead of "the" when referring to an object created from the current class:
- Gets this fractal flake's subdivision level rather
than Gets the fractal flake's subdivision level
- Write descriptions that add information to the entity names.
-
doxygen
Doxygen is a free, open source documentation system that supports
the main documentation syntaxes in a variety of programming languages.
Doxygen can automatically generate reference manuals in several
formats (including HTML and LaTeX) from a set of documented source
files.
Doxygen parses the code to extract specially formatted comments
that document code entities. In the process, Doxygen also extracts
the structure of the code and the relations between the various code
elements. These relations can be visualized through automatically
generated dependency graphs, inheritance diagrams and collaboration
diagrams.
Doxygen can also be used to generate human-written documentation
(beyond the automatically generated reference guide).
The Doxygen website has a list
of projects that use Doxygen for their documentation, and a short list
of particularly
nice examples.
inheritance (is-a)
- Implement is-a relationships through class inheritance
-
- Liskov's substitution principle
- An instance of a subclass can be substituted for an instance of the superclass without causing any problem
- Move common interface, data, behavior as high as possible in the inheritance tree
-
- Make all data
private, not protected
-
- Prefer polymorphism to extensive type checking
- Overridden virtual functions
- Never redefine an inherited non virtual function
- Don't re-use names of non-overridable base class routines in derived classes
- Never redefine an inherited default parameter value
-
- Be suspicious of classes that override a routine and do nothing inside the derived routine
-
- Avoid deep inheritance trees
-
- Avoid private inheritance
- Unless you know what you are doing
- Avoid multiple inheritance
- Unless you know what you are doing
member functions and data
- Implement has-a relationships through containment
-
- Keep class data members private
- Principle of encapsulation, aka information hiding
No data members in the public interface
- Avoid returning "handles" to internal data
-
- minimize indirect routine calls to other classes
- law of demeter: an object can call its own routines and the
routines of objects it creates, but should avoid calling routines of
objects provided by the objects instantiated.
- Know what functions C++ silently writes and calls
- Copy constructor, assignment operator, destructor, address-of operators, default constructor (if none provided) – all public!
- Disallow implicitly generated member functions and operators you don't want
- Make private
constructors and destructors
- Declare a copy constructor and an assignment operator for classes with dynamically allocated memory
-
- Prefer initialization to assignment in constructors
-
- Initialize all member data in all constructors, if possible
-
- Prefer deep copies to shallow copies until proven otherwise
-
assignment operators
- Have
operator= return a reference to *this
-
- Assign to all data members in
operator=
-
- Check for assignment to self in
operator=
- Logical entities declared within a component should not be defined outside that component.
-
- Avoid definitions with external linkage in the implementation file of a component that are not declared explicitly in the corresponding header file
headers
- The implementation file of every component should include its own header file as the first substantive line of code
-
- Clients should include header files providing required type definitions directly
- Except for non-private inheritance, avoid relying in one header file to include another
- Avoid accessing a definition with external linkage in another component via a local declaration
- Instead, include the header file for that component
file names
- The root names of the implementation and header files that comprise a component should match exactly
-
dependencies
- Avoid cyclic physical dependencies among components
- Acyclic physical dependencies can dramatically reduce link-time costs associated with developing, maintaining and testing large systems
(Lakos, 1996) John Lakos, Large Scale
C++ Software Design, Addison-Wesley Professional, 1996, ISBN-13:
978-0201633627.
(Meyers, 1997) Scott Meyers, Effective
C++, Second Edition, Addison-Wesley Professional, 1997, ISBN-13:
978-0201924886
Sun, Java documentation:
How
to write comments for the JavaDoc tool
Doxygen website: www.Doxygen.org