C/C++ Style Guide
Programming style is one of
the holy wars of
computer programming. However, regardless of your tastes one thing is
certain: when in Rome, do as the Romans do. That is,
learning the accepted style of a programming language is just as important as
learning the language itself.
It seems that every company or group has its own style guidelines (e.g.,
the linux
kernel C guide,
the Google
C++ style guide, or
the Mozilla
coding style guide). Regardless, for consistency's sake, we'll stick to a
set of common rules for our code. Correctly following the coding style will
be worth about 10% of the total grade for each homework.
(This guide borrows heavily from the OCaml style guide for old iterations of CIS 500.)
Formatting
- 80 Character Column Limit:
No line of code can have more than 80 columns.
Using more than 80 characters per line causes your code to wrap around to
the next line, which is devastating to the readability of your code.
- No Tab Characters: Do not use the tab character
(0x09). Instead, use spaces to control indenting. Ensure that your editor of
choice uses spaces when you insert a tab (i.e., soft tabs).
- Indent Two or Four Spaces: Most lines that indent
code should only indent by two or four spaces more than the previous line of
code. Pick one and be consistent.
- Line Breaks: Obviously the best way to stay
within the 80 character limit imposed by the rule above is pressing the enter
key every once in a while. Including empty lines should only be done between
declarations (e.g., class definitions or field/method definitions within
classes) and should be restricted to a single empty line at most.
If a declaration or expression extends over 80 characters, indent to the
opening delimeter of the declaration or expression. For example, the following
uses line breaks and indention to break up long parameter lists and boolean
expressions.
bool LargeMethod(FirstVeryLargeParamterType t1,
SecondVeryLargeParameterType t2,
ShortThirdParameterType t3)
{
return (SomeBooleanFunction(t1, t2) ||
AnotherBooleanFunction(t2, t3) ||
SomeBooleanFunction(t1, t3));
}
- Use K&R or Allman style braces: K&R looks like this:
namespace N {
class C {
void f() {
int x;
}
};
}
Allman style braces push the opening brace onto its own line:
namespace N
{
class C
{
void f()
{
int x;
}
};
}
Pick a style of braces and consistently use that style on any given
homework or the project.
Files Layout
If you're wondering what belongs in what file ("Where do my prototypes
go?" or "Do all my #defines need to be in one file?"), here are the
answers you seek. (Please note that everything said about .c files
also applies to .cpp files once we start using C++.)
- File that contains main(): This file
will in most cases be the shortest .c file. It shouldn't contain
much other than a few #include statements and the main()
function. And main() itself should be as short and easy to read
as possible -- this will help immensely with debugging. If you've written
a large chunk of code in main() that accomplishes a specific
purpose (e.g., reading in data from a file, printing out a set of
information, decoding a specific type of encryption, etc.) you should likely
break it off into an appropriately named function.
- Accesory .c files: Accessory
.c files contain the function definitions for the functions used
in main(). You may have more than one .c file in a given
program, especially as you break your functions down into groups (or as we
starting using classes in C++). Accesory .c files should only have
a #include for their matching .h file, although they may
also include a .h file that contains program-wide information (e.g.,
constants that all accessory files will need access to; or definitions for
structs used by multiple accessory files).
- Accesory .h files: Accesory
.h files are often the shortest files. They should contain
only function prototypes for their matching .c file, and any
constants (#define) and declarations (e.g., structs, typedefs)
needed.
Or, in short... where do you put? ...
- main(): In a file containing only main()
and a few #include statements
- function definitions: accessory.c files
- function prototypes: accessory.h files
- #define statements: accessory.h files
- struct definitions: accessory.h files
- typedefs: accessory.h files
- variable definitions: inside main()
function (not globally)
- #include statements: as needed
- comment containing your name: at the top
of every file, including .h files
Readability
- Aesthetics: This goes hand in hand with
limiting
columns to 80 characters -- strive to make your code aesthetically
pleasing, and it will also be more readable. A good example is below;
although both the left and right sides are the same exact variable
declarations, the right side is much easier to read with a quick glance.
int choice; int choice;
int i = 0; int i = 0;
float pi = 3.14159; float pi = 3.14159;
float e = 2.71828; float e = 2.71828;
char *str1 = "hello world"; char *str1 = "hello world";
char str2[] = "testing123"; char str2[] = "testing123";
- Spacing: Try to use the same rules as those
that are used in
CIS 110. For example, use a single space on either side of
mathematical operators, so that it looks like x + y
and not x+y, and only put spacing outside parentheses, not
inside, so it should look like a + (b + c) and not
a+(b + c) or a + ( b + c ).
- No "Magic Numbers": Using "magic numbers"
(numbers directly in code) can make code more difficult to parse, lead
to inadvertant errors, and is an enormous pain to deal with
if every instance of the number needs to be changed.
Instead, use #define or const to give the "magic number" a
meaningful name, and use that in your code instead. (As a rule of
thumb, "magic numbers" are any number other than 0 or 1, with the exception
of certain special cases, like in printf formatting).
- No Global Variables: Global variables are
variables that have global scope, and are accessible throughout the entirety
of the program, without needing to be passed to functions. They are also
more work than they are worth, and cause more problems than they fix, which
is why we won't be using them. If a variable is a constant value, use a
#define or const in a header instead. If not, use a local
variable, and pass it to functions as needed. Although there are exceptions,
they are few and far between.
- No Infinite Loops: Any loops you use in your
code must have clear terminating conditions.
This means no while(1) or for( ; ; ) loops, which require
you to look through the loop's entire body for the
necessary terminating condition and break; statement. Instead,
you may have to initialize the loop variable or perform a priming read
beforehand.
For example, instead of using the following code:
int n;
while (1) {
n = GetUserInput();
if ( (n >= 0) && (n <= 100) ) {
break;
}
}
You can either initialize the loop variable:
int n = -1;
while ( (n < 0) || (n > 100) ) {
n = GetUserInput();
}
Or perform a priming read:
int n = GetUserInput();
while ( (n < 0) || (n > 100) ) {
n = GetUserInput();
}
- No Break Statements: (Outside of those
used in switch statements, obviously.) Along with not using
while(1) loops,
your code should not need to use break; statements to accomplish
its goals. If you are having trouble avoiding a break statement in your
code, post a private question on Piazza with the code in question.
Naming
- Use meaningful names: Variable names should
describe what they are for. Distinguishing what a variable references is best
done by following a particular naming convention (see suggestion below).
Variable names should be words or combinations of words except in special
cases where single letter variables are understood,
e.g., i and j for loop variables.
- Case and naming conventions: The following
table outlines the capitalization conventions we will use for names:
Identifier |
Case |
Example |
Class |
Pascal Case |
AppDomain |
Method |
Pascal Case |
ToString |
Parameters, Locals, and Fields |
Camel Case |
myVariable |
Namespace |
Lower-case-underscores |
std::my_namespace |
Macros, #defines, and typedefs |
Angry |
MY_AWESOME_MACRO |
Definitions: Pascal case capitalizes the first letter of each word in the
name. Camel case is the exact same as Pascal, except it leaves the first
letter uncapitalized. Angry case is all-caps with words separate by
underscores.
- Don't abuse typedef, #include,
#define or using
namespace in header files: Only typedef types that would
be useful to someone who #includes the header file, because whatever
you typedef in a header file carries over to any file that includes
it. When it comes to using namespace, you usually
shouldn't put this line in the header file for the same reason --- it
automatically carries over to any file that #includes it, and could cause some
name clashes.
Comments
- Comments go above the code they reference: For
the sake of consistency (and to avoid issues with the 80 column limit), place
comments above code. (If the comment is super-short, you can get away with
placing it off to the side, but don't do it too often.) For example,
/* This is a well-placed comment */
#define AN_IMPORTANT_NUMBER 42
// This is fine in C++, but not in C
#define AN_IMPORTANT_NUMBER 42
#define AN_IMPORTANT_NUMBER 42 // This is fine too
versus other places, e.g.,
result = CallFunction(variable1, stringVar2, Var_3) // This is a badly placed comment off to the side -- it's way too long!
#define AN_IMPORTANT_NUMBER 42
/* This is a comment badly placed below */
- Take credit for your code: At the top of
each file, there should be a comment that includes the file name, your name and
Penn ID, and a short description of what the file contains/does. See
hello_world.c from Lecture 1 for an example.
- Avoid over-commenting: Incredibly long comments
are not very useful. Long comments should only appear at the top of a file,
where you briefly explain the code, credit the author(s), and reference any
sources that have more information about the algorithms/data structures if
necessary.
All other comments in the file should be as short as possible. After all,
brevity is the soul of wit. Most often the best place for any comment is just
before a function declaration. You should only need to comment inside the body
of a function if it is extremely long or complicated --- variable naming
should be enough in most cases.
- Avoid useless comments: Comments that merely
repeat the code they reference or state the obvious are a travesty to
programmers. Comments should state the invariants, the non-obvious, or any
references that have more information about the code.
Verbosity
- Don't rewrite existing code: The C and C++ standard
libraries have a number of functions and data structures --- use them
(unless otherwise stated)!
- Boolean Zen: Remember that the type of the
condition in an if-statement is bool. Avoid redundancy in your condition if you already have a value of type bool. For example
if (foo == true) { /* ... */ }
is redundant as foo must be a boolean variable. Therefore, we should rewrite this as
if (foo) { /* ... */ }
In Summary
The main points to remember when you're coding are as follows:
- Have a comment at the top of each file with at least your name and
some basic information.
- Do not use: global variables, "magic numbers," or
infinite/while(1) loops.
- Keep your code "clean" -- don't let lines wrap, and keep an eye out
for the underlying aesthetics.
- Be consistent! Pick a style and stick to it, for braces,
commenting, variable naming, indentation, spacing,
and anything else you can think of.