A solo programmer, working alone, has little need to adopt anything resembling consistent coding conventions. When no one else will ever see your code, you are the only person to benefit from any consistencies in it, or have your prgress hampered by any inconsistencies. Some large multi-programmer shops sidestep the issue by partitioning the developers into silos, assigning each of them exclusive ownership of some subset of the master source for a project; as long as interfaces are clearly established between subsystems, it doesn't matter a whit to them if the developers follow the same, or even similar conventions.
However, in the world that Art & Logic inhabits, our consistent coding style is a strategic strength; the ease with which our engineers can easily slip from language to language, project to project, and platform to platform gives us the ability to remain agile and highly responsive to the changing needs of our clients.
This document is an extension of the original programming style guide developed by A&L's founders before beginning work on their first project together for A&L in 1991. As the company has grown, the benefits of maintaining consistent style across projects has proven its value on an ongoing basis. It is also an encouraging sign that as the programming landscape has shifted since the company's founding, that original style has only required extension and fine tuning, not any serious rework.
In an ideal world, this document would tell you everything you need to know about the conventions used to write code while working at A&L. However, this world is not an ideal one.
When a client specifically requests that we adopt their coding conventions when working on their project, we will do so happily. In fact, the opposite case happens far more frequently, and more than a few current and former clients have adopted the A&L style (or a style derived from ours).
There may also be other reasons to deviate from this style guide. Consistency of programming style is important, but we must not encourage a foolish consistency. When deviations are appropriate, seek first to maintain consistency within each individual line of source, then within each function, each source file, and each project. In other words, keep deviations from A&L style as localized as possible.
Also, this document must be restricted to speaking in fairly broad strokes, as its main goal is to illustrate the conventions to follow in the most common cases. Any project of significant size will probably need to establish additional, local conventions to extend (but not replace) the practices outlined here. As an example, a large project that uses a complex class hierarchy should develop and consistently use a logical naming convention for the project. Once developed, any project-specific conventions should be documented in the project's ReadMe.txt file.
The most noticeable change in this release of the Art & Logic Programming Style Guide is that it is intentionally polyglot. The first version of this guide was explicitly a C++ style guide. The second major release (April 2000) was intended as a general style guide, but its emphasis on C++ (and failure to mention any other languages) led to confusion in some quarters, even to pockets of belief that the A&L style guide had absolutely no bearing on any language except C++. This release of the style guide is meant to correct those misunderstandings.
Languages that should be coded according to the principles outlined in this guide include the main programming languages that we use:
The examples in this document are intended to be as inclusive as possible; we count on the common sense of each engineer to ignore sections of this document covering features not found in their current language (for example, any discussions of preprocessor macros or C++ template formatting will have no impact on Java programmers). Similarly, we count on the common sense of each engineer to intelligently extrapolate the examples shown here to other languages that have features or constructs similar to those shown in the examples (for example, the rules for formatting of #include statements in C or C++ should be applied directly to the ordering of import statements in Python or Java.)
We also count on the common sense of our engineers to approach any work done in languages other than those on the above list by working to maintain the spirit of Art & Logic style, extending our style by applying the principles laid out here.
The other significant change from earlier versions of this guide is organizational.
This document is organized starting at an 'orbital view' of Art & Logic projects, and continues to drill down to finer and finer levels of detail as it progresses.
One aspect of Art & Logic's programming style that may catch some people off guard when first encountering it is our desire to make A&L code look like A&L code, regardless of the language being programmed. While this may seem odd when viewed from the position of a programmer working only in a language where different stylistic conventions are commonly used, it greatly eases the process of moving from project to project, and language to language here at Art & Logic.
While this Style Guide attempts to discuss programming style in as universal a way as possible, we also admit that each language we use will require some amount of specialization to adapt to the unique features, abilities, or oddities of each programming language used at Art & Logic. As a rule, we don't work with languages that stray too far from the ALGOL tree, so this approach works without too much difficulty. If we did a large amount of work in LISP, Forth, Smalltalk, or other languages that originate in different schools of thought, we might need to approach this issue differently.
This document contains numerous code snippets to illustrate the points being made; several of the languages used at A&L are used for these examples. Many of them are not complete pieces of code, and we count on the reader to supply the missing context that would permit these snippets to make sense.
In this document, code examples are formatted like this.
The largest unit of code organization at Art & Logic is the project; as a rule, code does not get written unless it is part of a project, whether for an external client or for internal use. Every project we work on should have a certain set of common features that allow new programmers to integrate smoothly into the workflow.
Every project should have a plain ASCII text file named ReadMe.txt that should reside in the topmost directory of the project's hierarchy. Unlike most of the other files developed for a project, this file is intended primarily for use only by other A&L engineers, and is typically excluded from any sets of deliverables that will be deployed to end users. It's fairly common for a second readme file to exist elsewhere in the project's source tree that is intended for distribution to the client or the client's end users.
The top-level ReadMe.txt file should be the main source of information likely to be needed by an engineer working on the project. Things that should be documented in a project's ReadMe.txt file include (but should not be limited to):
details on 3rd party SDKs or other tools required to build the project
path settings used in project makefiles
pointers to relevant external documents (standards, RFCs, etc)
pointers to internal documents (UML diagrams, other design documentation)
useful overview material
anything else that would be important information for a new programmer joining the project mid-stream. Try to prevent conversations like:
New programmer: "I installed the SDK, and downloaded all the project source, but I still can't get things to link..."
Project manager: "Oh, of course. You need to change {a registry setting | compile option | path | wave a dead cat over your head}."
As the project grows, the project manager is responsible for maintaining this document.
Every project needs to have a documented, repeatable release procedure. At the very least, this procedure needs to exist as a list of steps in the ReadMe.txt file that must be performed in sequence to issue a release to the client.
The preferred approach is to create an automated release procedure that implements the steps required to perform a release of the project as a separate program that performs each of the steps required to prepare the project for release to the client (or other end users of the project).
Common steps performed as part of a release procedure include:
Art & Logic assigns version numbers to software releases using a scheme based on the one used by Apple on the Macintosh. Every version code has a three part version number, followed by an optional release code and number, like:
<major#>.<minor>.<patchlevel>[<code><release>]
The major version number is used to indicate major changes in the visible interface. For instance, if a new release of a library has a different interface and is expected to break existing clients then it should have a new major version number. The major version number should not be incremented for bug fixes even if those bug fixes will break some programs, because it is a bug for a program to depend on a bug.
The minor version number should be incremented whenever a new feature (or set of new features) is added, but the new version is backward compatible. For libraries, this means any client binary that works with a previous minor version will continue to work with this new minor version. For both applications and libraries, this means that any existing data files, environment variable names, registry keys, etc. that the program reads will be usable with the new version. When the major version number increments, the minor version number resets to zero.
The patchlevel number should be incremented whenever a bug is fixed, or as part of the development cycle. No incompatibilities with existing clients or file formats are introduced, neither reading nor writing. When the minor version number increments, the patchlevel resets to zero.
The possible values for <code> are:
Code Description d Development release for client testing and status update a Alpha release--project is feature complete b Beta release--project is ready for testing in the field f Final release candidate(s)
The code and release numbers are used while the project is under development. While in each phase of a version's lifecycle, any release that is issued indicates both the phase and sequence of the release, with release numbers for each phase starting at 1.
When the client accepts a final release candidate, the version is released bearing a version identifier that has no <code><release> portion. The only allowable source difference between version X.Y.Zf# and X.Y.Z is the version number itself.
A simple project might have a release history that looks like:
Date Release Description 01 Jan 1.1.0d1 First development release issued to client, only partial feature set 01 Feb 1.1.0d2 Second development release, more features added 01 Mar 1.1.0d3 Third development release 05 Mar 1.1.0a1 First alpha release, feature complete 10 Mar 1.1.0a2 Second alpha release, including some bug fixes 01 Apr 1.1.0b1 First beta release, issued to client's trusted end users 07 Apr 1.1.0b2 Second beta release, bug fixes 15 Apr 1.1.0f1 First release candidate 17 Apr 1.1.0f2 Second release candidate (correcting a minor spelling error in the first candidate) 20 Apr 1.1.0 Actual final release issued to client after f2 candidate accepted by client.
Each release issued outside Art & Logic must be tagged in our CVS source control system with the release's version identifier. Art & Logic version identifiers are converted into a form that CVS will accept by using a tag name as follows (replacing the dots with dashes, prefixed with the word Release):
Release<major>-<minor>-<patchlevel><code><release>
For example:
Release1-1-0b1
Source files should be named using the same capitalization rules used elsewhere in A&L source code. Source files containing the implementation or interface of a class should be always have the same name as the class (dropping the leading A prefix ( see Reserved Prefixes) when present).
For example, the header file for the C++ class AHyperlink would be Hyperlink.h, and the implementation file would be named Hyperlink.cpp.
Source files that do not contain a single class should use a descriptive name that clearly identifies the contents of the file.
Every source file should begin with a single line indicating the copyright claimed for that file. Our standard contract lets A&L retain copyright on code that is not specific to a project. Any other code should claim a copyright on behalf of the client.
For example, code to read and parse a generic tab-delimited file should retain the A&L copyright. Code to read and parse a data file using a client's proprietary format should use the client's copyright.
All source files that are copyright A&L should include the following text in a comment as close to the top of the file as possible (where possible, this should be the very first line in each file):
// Copyright (c) <<current year>> Art & Logic, Inc. All Rights Reserved.
The project manager can provide the exact text to use for the client's copyright statement.
Immediately after the copyright notice, each source file should include this line, also inside a comment:
// $ Id: $
When the source file is checked into our source code management system, it will be expanded into the current file revision level and date/time of last check in, like:
// $ Id: SomeFile.java,v 1.3 2003/04/16 20:17:20 jcoder Exp $
If source will eventually be made visible to end users (for example, HTML files or configuration files that are included with end-user deliverables, the project manager may instead opt to use the RCSfile and Revision CVS keywords, which omit the identification of the last engineer to check the file in, like:
// $RCSfile $ $Revision $
As a rule, we do not use the CVS Log keyword, which expands to include the full history of check in comments for the file. If the project manager elects to use this keyword, it should be placed at the very end of the file so that the log information is available to anyone interested, but doesn't force readers of the code to page through years of modification history just to find the beginning of the source. Note that the CVS docs contain numerous warnings about using the Log keyword, especially with respect to problems created when a branch is merged onto the trunk.
When including or importing other source files, modules, or packages into a source file, the statements that perform that inclusion should be grouped together at the top of the source file, as follows:
For example, on a C++ project you might see a file that contains:
#include <fstream> #include <iomanip> #include <ios> #include <sstream> #include <time.h> #include "ColorDoc.h" #include "DrawingView.h" #include "Error.h" #include "UndoRedo.h"
In Java, you might see something like:
import java.io.* import java.util.* import com.ArtLogic.util.* import com.ClientName.*
There are two obvious exceptions to this layout:
Prefer to always have a single class per source file. An obvious exception to this is when developing exception classes that are tightly coupled to a specific class (in effect, becoming part of its interface). It may also make sense to group together a collection of small utility classes into a single source file.
When laying out classes, we use the following physical breakdown in the source files:
Within those sections, prefer to keep things grouped logically -- for instance, keep all of the overloaded operators together, all of the constructors together, etc.
One of our main activities as programmers is giving names to things. The most important principle we follow is that meaningful names are always preferred. Don't do this:
SInt32 xx; SInt32 xx1; SInt32 xx2;
instead, prefer meaningful variable names:
SInt32 elementCount; SInt32 xPosition; SInt32 currentTemperature;
However, it's not to be suggested that length alone is the important metric when choosing identifier names — clarity, memorability and consistency throughout a project are far more important. Generally, the shorter an identifier's life (or the more localized its scope), the shorter its name needs to be. For example, the traditional use of i and j as loop index names, or p for pointer values are idiomatic and their use should not be discouraged.
By establishing a simple set of capitalization standards, we can eliminate large amounts of time wasted trying to remember how to spell an identifier declared elsewhere in the source. Instead of using the words_separated_by_underscores style found in many programming textbooks, all identifiers in Art & Logic programs use one of several mixed case formats, as outlined below. (See also Reserved Prefixes)
Local variables and function parameters always begin with a lowercase letter. Subsequent words in the identifier are capitalized:
fileName xPosition
Class member variables always begin with a lowercase f followed by a single uppercase letter:
fFileName fStartTime
Class members in Python may (optionally) omit the f prefix, since the language requires that they are accessed by qualifying them with the current object's self.
Fields in simple C structures always begin with a lowercase letter, with subsequent words capitalized:
struct Point
{
SInt32 xPos;
SInt32 yPos;
};
Since the elements inside this structure may only be accessed using an instance of the structure (or a pointer to the structure) there's no ambiguity to be removed by an additional naming convention.
In Java, class methods (other than a class' constructor) begin with a lowercase letter, and additional words in the method name are capitalized. All method calls are made to an explicit object; calls to another method of the current object are made by specifying this:
public int getArea()
{
return this.getWidth() * this.getHeight();
}
In all other OO languages, method/member function names begin with an uppercase letter, and all subsequent words are also capitalized. Calls to member functions in the current object are made through the appropriate this-> or self. pointer:
public:
SInt32 GetArea()
{
return this->GetWidth() * this->GetHeight();
}
or:
def GetArea(self): return self.GetWidth() * self.GetHeight()
New types that are not classes (C struct, enumerated types, typedef s, etc.) are named using a leading uppercase letter, with subsequent words also capitalized, like:
enum Flavor
{
kVanilla = 0, // always initialize the first
kChocolate,
kCilantro,
kDurian // note — no comma on the last enum!
};
typedef unsigned short int IoRegister;
Identifiers in A&L programs should not contain underscores.
Avoid using two (or more) consecutive uppercase letters in an identifier. For example, when using an acronym or product name in an identifier, ignore the capitalization used elsewhere; a member variable holding some sort of data about something at NASA should be called fNasaData, not fNASAData. There are a few exceptions to this rule:
Ignore unusual capitalization found in product, technology or company names. For example, when working on a project spelled "megaSURF", any identifiers in the program that incorporate the project name should be capitalized like:
class AMegaSurfApp; void About(SInt32 megaSurfVersion);
Avoid using abbreviations when naming things in source code. When abbreviations are used in source, it's important to take care to prevent confusion arising from oddly chosen abbreviations, or from inconsistent use of them. For example, if you have decided that index is just too many letters to type as part of identifier names, decide once for the project whether you're going to spell it indx or idx and use that consistently throughout the project's code.
Attempt to limit abbreviations to those that are obvious, common, or idiomatic to the project.
We do not use Hungarian notation at Art & Logic.
To avoid name clashes, we use a set of reserved prefixes that indicate the use, scope, or function of the identifier. The letter following the prefix must be upper case. Note that this list of prefixes includes entries that are not applicable in all languages that we use; there are no global variables or functions in Java, for example, so the g and u prefixes listed below are not used. (See also Capitalization)
Recursive inclusion of C or C++ header files is prevented by wrapping the contents of all header files inside blocks of code like:
#ifndef h_FileName #define h_FileName // // contents of file here... // #endif // this must be the last line in the header file.
The identifier that follows the h_ prefix must be the name of the file, without its file extension.
Note that this is the only exception to the rule that A&L identifiers never contain underscores. In earlier versions of the A&L Style Guide, this prefix was just h, consistent with the format used for all of the other reserved prefixes. This exception was prompted by problems encountered when working with source code from outside Art & Logic that followed the Microsoft-style Hungarian convention that a variable prefixed with an h was a handle to something. After tiring of tracking down and fixing clashes between our include guard macro hSomething and a variable also named hSomething, we decided that altering this prefix was more likely to be a workable solution to the problem than convincing the rest of the world to stop using Hungarian notation.
All identifiers that refer to constant values should be prefixed with a lowercase k, whether they are declared using the C/C++ const keyword, as part of an enumerated type, the Java final keyword, or other equivalent mechanism.
Note that the k prefix is only intended to apply to identifiers that are apply to values that are constant at compile-time. Don't write code like:
SomeFunc(const std::string& kParam);
This should instead be:
SomeFunc(const std::string& param);
C/C++ compiler macros used to conditionally compile out sections of code should be prefixed with a lowercase q. Note that even though Java doesn't have preprocessor-based conditional compilation, javac and other Java compilers will omit any code from the binary that it can statically deduce will never be executed, accomplishing the same effect. So in Java, code like:
private static final qDebug = false;
if (qDebug)
{
// useful debug-only code here...
}
is the logical equivalent of C/C++ code like:
#define qDebug 0 #if qDebug // useful debug-only code here... #endif
C++ global functions are prefixed with a lowercase u. Avoid using global functions. Instead, group related functions into classes, using static member functions if necessary. Obvious exceptions to this rule are non-member operators and free functions that are logically part of a class' interface, but are not members. According to Sutter's "Interface Principle":
For a class X, all functions including free functions that both
- "Mention" X
- Are "supplied with" X
Are logically part of X, because they form part of the interface of X.
Any time you write a standalone function that is in global scope and is not clearly part of a class' interface, it should use the u prefix. Free functions that are contained within a namespace are not global, and therefore do not need to use this prefix.
In C and C++ code, use the AlTypes.h typedefs when referring to integral types. These typedefs provide an easy way to specify the word size of an integer variable in a cross-platform manner. The typedefs found in the AlTypes.h file provide definitions for the platforms most commonly used at A&L; when starting a project on a platform that's not yet represented in AlTypes.h, please provide definitions.
The types in AlTypes.h are:
typedef range typical definition SInt8 -128..127 signed char UInt8 0..255 unsigned char SInt16 -32768..32767 short UInt16 0..65535 unsigned short SInt32 -2^31..(2^31)-1 long int UInt32 0..2^32 unsigned long int SInt64 -2^63..(2^63)-1 long long int UInt64 0..2^64 unsigned long long int
Note that not all legacy or embedded platforms provide the underlying support for 64-bit data types.
Since more time is spent reading code than writing it, having clear, thorough documentation is crucial both during initial development and for successful long-term maintenance. The goal is not redundancy; commenting every line of code like:
// increment i by 1 ++i;
just raises the noise floor and provides no useful information. However, comments like:
public boolean addMidiEvent(MidiEvent event)
{
// add the timestamped MidiEvent 'event' at the correct place in our
// event list. We are using an array representation of a heap structure
// (see Sedgewick, "Algorithms in Java", chapter 9). This algorithm is
// guaranteed to be at worst O(2 log(n))
// code left as an exercise for the reader...
}
both explains the design and intent of the code that follows, along with a pointer to an external reference explaining the design and theory behind the underlying data structure.
The primary function of comments in header files is to provide documentation for programmers who will be using the classes you write. Ideally, other programmers should never need to refer to your implementation files to make proper use of your code.
In languages that do not use separate header files, the same principles obtain; the interface must be documented at a sufficiently high level for users of the code. In Java, feel free to use Javadoc style comments; in Python, all publically callable functions should include docstrings.
Make sure that the following things are well and correctly commented:
- overall class design / usage information
- member function usage
- function parameters
- return values
- any special constraints
Comments for member functions precede the function declaration, and are not followed by a blank line.
// The `ASafeBuffer' class wraps the standard library std::deque class.
// Most of the functions simply delegate to the appropriate
// std::deque function. Note that we do NOT derive publically from std::deque
// — the lack of a virtual destructor makes that unsafe.
class ASafeBuffer
{
public:
enum BufferState
{
kSuccess = 0,
kNotInitialized,
kFatalError
};
// construction, assignment and destruction...
ASafeBuffer();
ASafeBuffer(const ASafeBuffer& x);
ASafeBuffer& operator=(const ASafeBuffer& x);
~ASafeBuffer();
// appends the value 'val' to the end of the buffer.
// returns ASafeBuffer::kSuccess unless there was an
// initialization problem.
BufferState Append(UInt8 val);
private:
std::deque fBuffer; // comment member variables here...
// ...or place larger comments for member variables
// before their declaration.
SInt32 fBufferSize;
};
The primary audience for comments in your implementation files is the maintenance programmers who will be working on your code in the future. Most likely, you will be that maintenance programmer, which should give you extra cause to be charitable with your comments.
Comments precede the code being documented, and are at the same indentation level. The comments should not just echo the code in prose, but should discuss the intent of the code. In Code Complete, McConnell recommends implementing functions by writing the comments first, then going back and actually writing the code. The approach is sound — if you don't understand the requirements of the function well enough to write it as a narrative first, you don't yet have a firm enough design to write code from.
We have a few conventions that we use to encode additional information into comments. Using a standard format simplifies searching for these markups.
//!!! indicates that attention needs to be paid to the next line or section of code. Typical reasons include calling attention to placeholder code, or code that is known to be temporarily incomplete. Use ellipsis dots to indicate larger areas:
//!!!... // this code temporarily removed pending resolution of bug 9xxx. // (several lines of code) //...!!!
//$$$ indicates a request that the following code be reviewed before a change is considered complete. This practice began before A&L used source code management software, and we used this technique when making changes in a source file 'owned' by another programmer, who would review the requested changes before merging them into the master sources. Since most multi-programmer projects are still divided such that each source file has a primary author, this technique is still a useful way to mark changed code for review by someone with more experience with the code. The original code should be commented out, but retained as a reference:
//$$$ before
// for (SInt32 i = 0; i < kLoopLimit; ++i)
// {
// this->Process(i);
// }
//$$$ after
// doesn't the process function expect 1-based indexing?
for (SInt32 i = 0; i < kLoopLimit; ++i)
{
this->Process(i + 1);
}
//$$$ end
All indentation is done using space characters (0x20), not literal tabs (0x09), using three spaces per logical tab stop. Set your editor to always save files in this format.
Preprocessor directives and labels used as the target of a goto statement are always placed in the first column, regardless of the current ambient indentation level:
bool ok = true;
for (SInt32 i = 0; i < kSomeConstant; ++i)
{
for (SInt32 j = 0; j < kSomeOtherConstant; ++j)
{
#ifdef qVerbose
std::clog << "testing: " << i << ", " << j << std::endl;
#endif
ok = this->Test(i, j);
if (!ok)
{
std::cerr << "Fatal error testing: " << i << ", " << j << std::endl;
goto CleanUp;
}
}
}
return true;
CleanUp:
// cleanup code here...
return false;
Other constructs are indented as outlined below.
Avoid using source lines longer than 79 characters. Occasional violations of this item are acceptable when the resulting code avoids an awkward line break or is otherwise more readable as a single long line. Very long lines due to deep indentation levels may be a sign that deeply nested code should be refactored out into a separate function.
Logical lines that are longer than 79 characters should be broken at the most logical point and continued on the next line, indented an additional single space, like:
1 2 3 4 5 6 7
1234567890123456789012345678901234567890123456789012345678901234567890123456789
retval = FunctionWithManyArguments(longArgumentName1, longArgumentName2,
longArgumentName3, longArgumentName4);
An alternative layout that's taller rather than wider is:
retval = FunctionWithManyArguments(longArgumentName1, longArgumentName2, longArgumentName3, longArgumentName4);
or:
retval = FunctionWithManyArguments( longArgumentName1, longArgumentName2, longArgumentName3, longArgumentName4 );
The nice thing about using this tall layout is that it makes it easy to add comments that identify each of the arguments made to a function with a large number of parameters:
retval = FunctionWithManyArguments( longArgumentName1, // filename longArgumentName2, // access mode longArgumentName3, // share mode longArgumentName4 // security );
Lines containing expressions should be broken as naturally as possible. Where possible, break after a comma:
retval = anObject.SomeUsefulFunction(x.LeftMargin() + x.RightMargin(), x.TopMargin() + x.BottomMargin() - 1);
If that's not appropriate or feasible, break after an operator:
retval = anObject.SomeUsefulFunction(x.LeftMargin() + x.RightMargin() + anObject.HorizontalPadding(), x.TopMargin() + x.BottomMargin() - 1);
Only use the \ backslash line-continuation mechanism when it's not possible to find a cleaner place to break. As McConnell puts it in Code Complete:
Make the incompleteness of a statement obvio .
if (someBoolean)
{
// code to execute when true
}
else if (someOtherBoolean)
{
// some other code to execute
}
else
{
// code to execute in other cases...
}
for (int i = 0; i < kSomeConstant; ++i)
{
// loop code here...
}
Note that we prefer to use the pre-increment ++i as a general habit. It is guaranteed in all languages that use this construct to work identically to the more commonly seen post-increment i++ that dates back to the earliest days of the C language. In C++, however, maintaining this habit prevents possible inefficiencies when looping over an object that exposes an iteratable interface. Consider the likely cost difference between:
typedef std::vector MyVector;
MyVector vec;
// assume the vector gets filled here....
for (MyVector::const_iterator i = vec.begin(); i != vec.end(); i++)
{
// clever and important code here...
}
and:
for (MyVector::const_iterator i = vec.begin(); i != vec.end(); ++i)
{
// even more clever and important code here...
}
Yes, as Knuth said:
We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.
but one of the benefits of building up habits is that they let us forget things, but still obtain their advantages.
while (someBoolean)
{
// useful code...
}
If the body of the while() is empty, make that explicit, as:
while (someFuncReturningBool())
{
// intentionally empty
}
not:
while (someFuncReturningBool());
do
{
// loop code here...
} while (someBoolean);
Case statements inside a switch statement are indented one tab stop, and the code for each case is at the same indentation level:
switch (midiEventType)
{
// comments precede the case
case kNoteOn:
this->NoteOn(now);
if (duration > 0)
{
this->NoteOff(now + duration);
}
break;
case kNoteOff:
this->NoteOff(now);
break;
case kPolyAfter:
after = channelValue;
// break; /// include, but comment out the break when falling through intentionally.
case kChannelAfter:
this->AfterTouch(after);
break;
case kPitchbend: // no need to include a commented out break when falling through an empty case
// blank line...
default:
ASSERT(false);
this->LogError("Unsupported event type!");
break;
}
An alternative layout to the above follows, which places the body of each case handler in its own scope. Not only is this more attractive visually, it provides an easy way to declare local variables that are restricted to the scope of an individual case:
switch (midiEventType)
{
// comments precede the case
case kNoteOn:
{
this->NoteOn(now);
if (duration > 0)
{
this->NoteOff(now + duration);
}
}
break;
case kNoteOff:
{
this->NoteOff(now);
}
break;
case kPolyAfter:
{
after = channelValue;
}
// break; // include, but comment out the break when falling through intentionally.
case kChannelAfter:
{
this->AfterTouch(after);
}
break;
case kPitchbend: // no need to include a commented out break when falling through an empty case
// blank line...
default:
{
ASSERT(false);
this->LogError("Unsupported event type!");
}
break;
Switch statements should always have a default case. If the default case should never be executed, place an ASSERT(false); to make sure that this error is caught during debugging and testing.
Declare variables as close to the point of first use as possible, rather than in a single big block at the top of the current scope (unless you're programming in C).
Only declare one variable per line of source code -- don't do this:
// NOTE that the following line of code is probably not what the programmer // intended to write, as 'xPos' is a pointer-to-UInt8, but 'yPos' is a // UInt8. UInt8* xPos, yPos; double yaw, pitch, roll;
instead, do:
UInt8* xPos = NULL; UInt8* yPos = NULL; double yaw = 0.; double pitch = 0.; double roll = 0.;
Avoid leaving variables uninitialized to avoid the possibility of errors resulting from those variables being accessed while filled with garbage.
Matching curly braces should always be aligned in the same column. In general, this means that curly braces should always be on a line by themselves:
if (someBoolean)
{
someVariable += 1;
}
not:
if (someBoolean) {
someVariable += 1;
}
All code inside the braces is indented one tab stop.
The only exception to the 'braces alone on a line' rule is found when using a do/while loop:
do
{
// some intelligent and useful code here...
} while (someCondition);
In this construct, placing the closing brace and the while() on the same line make it clear that the while expression is tied to the preceding scope, which would not be otherwise visually obvious.
In general, prefer to use curly braces even in situations where the language does not require their use, such as very simple if`/``else statements or for loops:
if (someBoolean)
{
functionCall();
}
for (int i = 0; i < kLimit; ++i)
{
fBuffer[i] = 0;
}
not:
if (someBoolean) functionCall(); for (int i = 0; i < kLimit; ++i) fBuffer[i] = 0;
to avoid maintenance problems in the future.
Another case where "unnecessary" braces should be used is when writing an empty while loop:
while (*p++ = *q++)
{
// this loop intentionally left empty...
}
instead of the form that is more commonly found:
while (*p++ = *q++);
By prohibiting this common loop format, we can easily check for cases where legal (but wrong) code like:
int i = 0;
while (i++ < kLoopLimit);
{
myBuffer[i] = 0;
}
performs a loop with no body, then executes the intended body of the loop exactly once. Python programmers can stop chuckling now.
Language keywords are followed by a single space unless they are followed by a semicolon, like break; or return;
There is never a space between a function or method name and its opening parenthesis. Other opening parentheses should be preceded by a space.
Commas are followed by a single space, and are never preceded by a space.
Semicolons are never preceded by a space. If code follows a semicolon on the same line (as in the control structure for a for() loop, the semicolon is followed by a space.
( and [ are not followed by a space, and ) and ] are not preceded by a space. An exception to this may be made to make grouping more apparent when using deeply nested parentheses:
if ( ((a == b) && (c == d)) || ((e == f) && (g == h)) )
Binary operators are separated from both operands by a single space.
Unary operators are not separated from their operand.
When declaring pointer variables in C / C++ or reference variables in C++, the * and & operators are attached to the typename, not the identifier, like:
UInt8* buffer; void AClass::AClass(const AClass& x);
not:
// don't do this: UInt8 *buffer; void AClass::AClass(const AClass &x); // or this: UInt8 * buffer; void AClass::AClass(const AClass & x);
Inside functions, use blank lines judiciously to group lines of code into logical chunks.
Between functions, use blank lines judiciously to visually separate the functions.
Always use hexadecimal constants or literal values when working with bitwise operators:
// see if this is a MIDI status or data byte...
if (dataByte & 0x80)
{
// handle status byte
}
never:
// see if this is a MIDI status or data byte...
if (dataByte & 128)
{
// handle status byte
}
Don't rely on the fact that many operations may silently be coerced by the compiler into boolean values. Always make the comparison explicit:
if (pointer != NULL)
{
// code...
}
not:
if (pointer)
{
// code
}
However, if you are in fact working with real boolean values, there is no need to labor the point -- code like:
if (truthVal)
or:
if (!truthval)
is fine -- you don't need to write code like:
if (true == truthVal)
or:
if (false == truthval)
To avoid a common bug, always place a constant before a variable when performing equivalence testing:
if (kSentinelValue == val)
instead of:
if (val == kSentinelValue)
which would compile without a hiccup if the == were mistakenly typed as =:
if (val = kSentinelValue)
{
// note that the above "test" always succeeds, and that 'val' is
// now always set to 'kSentinelVal'. Oops.
}
Design Patterns : Elements of Reusable Object-Oriented Software By Gamma, Erich et. al. 395 Pages Published by Addison Wesley Date Published: 09/1994 ISBN: 0201633612 Literate Programming By Knuth, Donald Ervin Published by CSLI Publishing Date Published: 05/1992 ISBN: 0521073806 Writing Solid Code By Maguire, Steve 256 Pages Published by Microsoft Press Date Published: 05/1993 ISBN: 1556155514 Code Complete : A Practical Handbook of Software Construction By McConnell, Steve M. 857 Pages Published by Microsoft Press Date Published: 05/1993 ISBN: 1556154844 Practical Software Configuration Management : The Latenight Developer's Handbook By Mikkelson, Tim / Pherigo, Suzanne 301 Pages Published by Prentice Hall Date Published: 04/1997 ISBN: 0132408546 Elements of Style, Fourth Edition By Strunk, William Jr. / White, E. B. 128 Pages Published by Prentice Hall Date Published: 08/1999 ISBN: 0205313426
The Art & Logic Programming Style Guide is Copyright (c) 2003 by Art & Logic, Inc. All Rights Reserved.
This document was authored using the reStructured Text markup language and the corresponding Docutils processing system. Information available at http://docutils.sourceforge.net
$Log: styleguide.html,v $ Revision 1.1 2003/05/02 21:16:28 bporter Style Guide v3.x, as released. Revision 1.10 2003/05/01 15:21:38 bporter Corrected text re breaking lines around operators (thx to Jason Bagley) Revision 1.9 2003/04/23 16:43:26 bporter Fixing some minor formatting problems. Revision 1.8 2003/04/23 16:18:29 bporter integrating Mark's suggested explanation of the mechanics behind version/release numbers. Revision 1.7 2003/04/21 14:10:33 bporter Added some new text re: other languages, improved some formatting. Revision 1.6 2003/04/18 21:01:11 bporter Integrating more fixes after Mike's comments. Revision 1.5 2003/04/18 20:37:16 bporter Folding in Mike's comments, leftover discussions from the StyleGuide conference, fleshing things out some more Revision 1.4 2003/04/16 20:18:37 bporter Testing to see if I can sneak CVS keywords in without having them expanded... Revision 1.3 2003/04/16 20:17:20 bporter Testing to see if I can sneak CVS keywords in without having them expanded... Revision 1.2 2003/04/16 19:43:38 bporter Nearing the end of a releasable draft... Revision 1.1 2003/03/28 17:18:44 bporter First commit, next rev of the style guide