CS 11

Style Guide

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

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:

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:

  1. The name of the program file.
  2. A succinct description of the program's purpose.
  3. Your name as the author of the program.
  4. Credits for anyone who helped you.
  5. 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:

In general, comments should be useful to the person reading your code. They should explain why something is the way it is. They should not be a narration of how the code works — that is what the code itself describes.

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 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:

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:

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)

Mark A. Sheldon (msheldon@cs.tufts.edu)
Last Modified 2022-Jun-13