Table of Contents
Program code is written for humans to read. Yes, it can (hopefully!) run on a computer, but computers don't care about style or clarity. They'll take anything. Your code must be readable/interpretable by people. The code that you submit is expected to adhere to the following style guidelines. Some of these items will not apply until later assignments, but the majority of them can be put into practice immediately. They include:- Indentation
- Statements and Spacing
- Line Length
- Naming Conventions
- Commenting
- Global variables are not allowed
- Booleans, tests, conditionals, loops
- Function Conventions
- Pointers
Indentation
Every time a new block of code is introduced via a curly brace, the code within it should be indented at least 4 or 8 spaces more than the code in the next outer block. The number of spaces that you use is a matter of taste, so long as that number is used consistently throughout your program. This makes it extremely easy to see which code is in the same block and is so fundamental to coding style that you would likely have trouble reading your own code without it!Note that many editors default to 2 spaces per indentation. This is not enough. Tired eyes need more — at least 4.
Example:
int main(int argc, int argv[]) { cout << "First outer block, one level of indentation"; if (...) { cout << "Second block, two levels of indentation"; if (...) { cout << "Third block, three levels of indentation"; } } }
Always use spaces instead of tabs for your indentation, as different editors interpret and display tabs differently. Good news: Atom can do this for you. Atom calls this “soft tabs.”When you hit the tab button, Atom inserts 2 spaces by default (yuk!). You can change the number of spaces by going to the Settings tab (which you can also get to via Atom → Preferences on a Mac) and then go to Editor → Tab Length. Set it to 4 or 8. While you're here, you can choose Soft tabs, too.
In other editors, you will have to search to find the appropriate settings.
Statements and Spacing
C++ doesn’t care if you put multiple statements on a single line or if you squeeze all your program code together but we do!
A statement in C++ is simply some instruction that ends in a semicolon, so it could be an assignment, a call to a function, or any number of interesting things. Each statement should go on its own line.
Always put spaces around binary operators
(like +
, =
, etc.) as well as after
keywords (like if
and for
), and before
open braces.
You should also strive to separate your code into logical sections using blank lines. This will make it much easier to locate a particular section of code.
Example:double sum_elems(double arr[], int length) { double sum = 0; int i; // Loop through array, accumulate sum for (i = 0; i < length; ++i) { sum += arr[i]; } return sum; }
See how the above code has clear sections and items are spaced out. A terrible example would be:
double sum_elems(double arr[],int length){
double sum = 0; int i;
// Loop through array, accumulate sum ← BAD!!
for (i=0;i<length;++i){sum += arr[i];}
return sum;
}
Line Length
A single line of code should not exceed 80 characters in length. In Atom, you can set lines to wrap at 80 characters using Settings → Editor → Soft Wrap At Preferred Line Length (the default is 80), which will make it obvious when you exceed this limit.
Note that changing the size of your editor window does not change the length of the lines saved in the file. When Atom wraps your lines, it's just helping not have to scroll horizontally, but the line in the file in longer than the screen width. You can tell in Atom, because there will be a dot rather than a line to the left of a wrapped line.
You must use your return/enter key to put explicit line breaks in your code.
Do not remove spaces or use cryptic variable names to make a line shorter. It is usually possible to break a line up so that, for example, a bunch of things added together are one item per line or function arguments are placed one per line and lined up nicely.
To see why this makes sense, make your browser window as wide as possible, then click the Make wide button in the upper right. Is that easier to read? Now try the Make really wide button. You probably don't enjoy horizontal scrolling. The graders don't either, and they frequently have other windows open on their screen. Don't make them scroll horizontally! You can hit the Make readable button to make life easier again.
Name-Calling
A rose by any other word would smell as sweet? Not in this class. One of the most difficult parts of writing good, clean code is naming things well. It's hard to think of short, descriptive names for variables, functions, and classes. We want the names to describe what we're trying to represent without being too wordy. Here are some general guidelines:- Variable names should either
- be all lowercase, words separated by underscores (e.g.,
next_word
), or - be in camelcase
(e.g.,
nextWord
).
- be all lowercase, words separated by underscores (e.g.,
- Function names should follow the same rules as variable names, and should not include information about the return type. That's built in.
- Class names should start with capital letters and be single words, if possible. If this is impossible, do your best to get there or use shorthand. CamelCase is used if there are mulitple words.
- Reserve the use of vague names for loop counters and other
bits of code that don't have much to do with the real work of your
program. That is, use
i
for a loop counter instead of a variable calledloop_counter
.
We can't give much advice as to how to come up with great
names. This comes with practice and with context. For example,
in encrypt.cpp
we were being overly-luscious with
variable names in order to make the code as interpretable as
possible!
Commenting
Reading code alone is not something we can do super well until we've spent months (if not years) reading it and know most of the tricks of the trade, and even then someone is likely to have used some feature or pun that you weren't aware of. This is why we need good comments; comments let you as a programmer express some of the thought process that went into your code.
Every C++ program you write in CS 11 must start with a header comment that lists at least these pieces of information:
- The name of the program file.
- A succinct description of the program's purpose.
- Your name as the author of the program.
- Credits for anyone who helped you.
- Any known bugs or issues.
Other comments should occur frequently enough in your code to be informative, but not so frequently as to be overwhelming. This can take some practice! Some examples of places comments belong:
- Above a coherent section of code (such as those you might put a blank line between).
- Above variable declarations (one comment for a group of variable declarations suffices).
- Above a calculation, if it’s complex and/or unclear why it’s needed (though a documented function would be better if it's really that complex!).
- Above function definitions (
main()
is the exception; see below for more info) - Above a
for
orwhile
loop (usually unnecessary inside the loop)
The rest of this section may be ignored until you write functions.
Each function you write in this class is expected to have a function contract: a block comment above the function definition that explains how the function operates. By providing a function contract, you can both keep track of what your own function should be doing and convey its meaning and purpose quickly to readers of your code. Furthermore, coming up with the contract first can simplify the implementation process immensely (i.e., it makes the code easier to write since the contract tells you what the code should do). Every function contract should list the following components:
- The name of the function.
- A succinct description of the function's job or purpose.
- The arguments the function expects. Aim to describe what those arguments represent.
- What the function returns, if anything. Describe what the returned value represents, not just its type or a variable name.
- What other effects it has, for example if it does any program input/output or modifies the state of an object.
The contract should not narrate the code or describe internal matters like whether a function contains a loop. Just put in the information someone who calls the function needs to know.
Here are some examples of function contracts. You should be able to understand what the function will do even without seeing its code!
/* distance * Parameters: Two points on the plane (x, y) and (u, v). * Purpose: Compute the distance between points (x, y) and (u, v). * Returns: The distance between the two points. */ double distance(double x, double y, double u, double v) { // … body of function … }
/* print_menu * Parameters: None (so this function always computes the same thing). * Purpose: Print out a menu of options for the user. * Effects: Prints a message to standard output. */ void print_menu() { // … body of function … }
/* read_line * Parameters: Name of a file to read * Pointer to first slot of an array to store characters in * Maximum number characters that can be stored in array * Purpose: Read the first line of text from the file named by * 'filename' and store that text in the character array * pointed whose first element is pointed to by 'words'. * Returns: Number of characters read in * Effects: Reads up to max_size characters from file * Stores up to max_size characters in words * Notes: Only the first max_size characters of the line * will be read if the first line is too long to fit * in the array. * The ending newline character will be stored if it fits. */ int read_line(string filename, char *words, int max_size) { // … body of function … }
Global variables are not allowed
In this course, global variables are forbidden in your programs. A global variable is one that is declared outside of any function (and is thus available to all functions that follow the variable declaration).
Global constants are acceptable if they name a generally useful data item. For example, you may put, at the top of you program a definition like this:
// Nautical miles per kilometer const double NM_PER_KM = 1.852;
Note the use of const
, which means that this is a
constant: the value stored there cannot be changed.
Defining a global variable that is non-constant is prohibited.
Students often want to know why. Global variables:
- are icky
- provide a way to avoid learning how function parameters and return values work, thus undermining a central pedagogical goal of the course
- make functions less modular and harder to understand by introducing dependencies among them (and thus the order they are used in) that is not evident from their signatures.
- make functions less modular by requiring clients to define that variable and not use its name for something else in their application
- (more advanced) make the functions unsafe in concurrent environments
- are a scourge to be eliminated from our programs
Booleans, tests, conditionals, loops
In the early days, C and C++ did not have a boolean type. C++
gained the bool
type in the late 1990s, and so we
should use it.
All tests in conditionals (if
statements and
conditional expressions) and loop tests should produce a
boolean value. Do not use integers or pointers or any
other non-boolean value in a test. E. g., if n
is
an integer variable and p
is some type of pointer
variable,
write “if (n != 0)
” rather
than “if (n)
”. Likewise,
write “if (p != nullptr)
” rather
than “if (p)
”
Use true
and false
rather
than 1
and 0
.
There is no need to compare a boolean value
to true
or false
. Write
if (isBig)
or if (not isBig)
rather than
if (isBig == true)
or if (isBig == false)
Use the
keywords and
, or
, not
rather than the pre-21st century
operators &&
, ||
, !
This makes code easier to read and less prone to
difficult-to-spot bugs caused by typos. (Note: You should
use !=
when testing for inequality.)
The break
keyword should only be used in
a switch
statement. In addition to making loops
more difficult for a reader to understand, it undermines our
goal of teaching students to write loop conditions
thoughtfully. The continue
keyword may be used
occasionally, but should be avoided when possible.
Returning from the middle of a loop, however, is often useful and encouraged.
Function Conventions
As programmers, we care about modularity and abstraction: We strive to write code that can be reused to solve other problems and that hides unimportant details from the people using our code. This first point should immediately make you think of functions; we define small sub-operations that each take some small, easy to solve problem and solves it in a nice little package that we can use wherever we need it with the right inputs.
There are some style guidelines that actually help us adhere to principles of abstraction and modularity more closely:
- Functions should have a single purpose.
- Functions should have at most about 5–10 local variables.
- Functions should be short: less than or equal to 30 lines (including everything between the function's curly braces). They should ideally fit on one of those 24-line screens you get when you open a terminal on the Halligan machines.
- Functions should not have more than 3 or 4 levels of indentation.
Shorter functions are generally simpler; they have a single purpose and don't do a million things. Having a single purpose forces you to think harder about reusing code, and when it is appropriate to separate the work into more than one function. Having a huge number of variables in your function usually indicates you've added too much complexity, and should consider writing another function or two. Another indication that your funtion is too complex is that there are more than 3 or 4 levels of indentation.
Pointers
Write nullptr
for the null pointer and
not NULL
, and especially not 0
.
When declaring a pointer variable, the asterisk goes with the variable
like this:
int *np1;
and not next to the type.
int* np1;
Why? In C++, the *
is treated like []
and ()
in a declaration: they show how the variable will
be used to get a value of the type. For example,
int* np1, np2;
declares one pointer variable (np1
) and one
integer variable np2
!
Pointers are not booleans and so should not appear alone in a test.
Explicitly compare pointers to the null pointer like this:
while (p != nullptr)