Style Guide

Written by by Nick Troccoli, based on writeups by Matthew Trost, Julie Zelenski and others

These are some of the general style qualities that we expect your programs to have in order to receive full credit. This is not an exhaustive list; please also refer to each assignment spec for other style practices to follow. Certainly it is possible to write good code that violates these guidelines, and you may feel free to contact us if you are unclear about or disagree with some of them. But we do expect you to follow these rules (unless there is an error in this document). In most professional work environments you are expected to follow that company's style standards. Learning to carefully obey a style guide, and writing code with a group of other developers where the style is consistent among them, are valuable job skills.

This document is a work in progress.

Any guidelines written here are in addition to what is mentioned in the given assignment's spec, so you are responsible for reading that spec and following its instructions. If there is ever a conflict between this style guide and an assignment spec, follow the assignment spec.

Whitespace and Indentation

  • Indenting: Increase your indentation by one increment on each brace {, and decrease it once on each closing brace }.
  • Use 2-4 spaces per indent level, being consistent, and use spaces instead of tabs for editor consistency.
  • Place a line break after every {.
  • Do not place more than one statement on the same line.
// bad style
int x = 3, y = 7;  double z = 4.25;  x++;
if (a == b) { foo(); }
// good style
int x = 3;
int y = 7;
double z = 4.25;

x++;
if (a == b) {
    foo();
}
  • Long Lines: When any line is longer than 120 characters, break it into two lines by pressing Enter after an operator and resuming on the next line. Indent the trailing second part of the line by two increments (e.g. two tabs). For example:
int result = reallyLongFunctionOne() + reallyLongFunctionTwo() +
        reallyLongFunctionThree() + reallyLongFunctionFour();

int result2 = reallyLongFunction(parameterOne, parameterTwo, 
        parameterThree, parameterFour, parameterFive);
  • Expressions: Place a space between operators and their operands.
int x = (a + b) * c / d + foo();
  • Parameters: Place a space between elements separated by commas.
myFunction(x, y, z) // not myFunction(x,y,z)
  • Blank Lines: Place a blank line between functions and between groups of statements.
void foo() {
    ...
}
                            // this blank line here
void bar() {
    ...
}

Naming and Variables

  • Structs: When using structs, use a typedef to avoid having to type the struct keyword whenever you declare a new variable of the struct type.
typedef struct MyStruct {
    ...
} MyStruct;

...

// Because we used the syntax above, now we can say
MyStruct s = ...

// instead of
struct MyStruct s = ...
  • Names: Give variables descriptive names, such as firstName or accountStatus. Avoid one-letter names like x or c, except for loop counter variables such as i.
  • Capitalization: Be consistent in your name capitalization. Name variables, functions, structs, etc. using either camel-casing likeThis, or using snake-casing like_this. Always name constants in uppercase LIKE_THIS.
  • Scope: Declare variables in the narrowest possible scope. For example, if a variable is used only inside a specific if statement, declare it inside that if statement rather than at the top of the function or at the top of the file.
  • Types: Choose appropriate data types for your variables. For example, if a given variable can store only integers, give it type int rather than double.
  • Constants: If a particular constant value is used frequently in your code, or if it has special significance, declare it as a constant using const or #DEFINE at the top of your program, and always refer to the constant in the rest of your code rather than referring to the corresponding value. Or, use sizeof if it is a known value. Don't have "magic numbers", which are hardcoded numbers that seem to be "magically chosen" because it's not necessarily clear to another reader why that particular value is being used. That being said, you don't need to make a constant to replace every number literal hardcoded into your program. A good general rule of thumb is to ask yourself whether the number represents something more significant than just the hardcoded number itself. For instance, if you have a for loop like this:
for (int i = 0; i < NUM_ITERATIONS; i++) {
...

The 0 probably doesn't need to be a constant, because it's representing just the literal number 0. However, the limit here represents the number of iterations in addition to its actual value (E.g. 8) so that is a good thing to make a constant. Another way to think about it is, if someone else is reading your code, would that reader have a hard time understanding the significance of the hardcoded number? If so, that may be a sign that a constant is a good idea. Constants must be named with ALL_CAPS and underscores. The names of constants must be descriptive - if they cannot sufficiently describe the purpose of the constant, then you can add a comment above them to further explain what they are for and/or why they have the value they do.

const int MAX_RESPONSE_LENGTH = 100;
// or
#define MAX_RESPONSE_LENGTH 100;

There are some cases where an inline comment would suffice to document the value if it only appears once - too many constants can pollute your code and force the reader to constantly cross-reference the constants at the top of the file as they read. But, even if a value only appears once, it can at times still be useful to document it as a constant if it is sufficiently important and where perhaps someone would want to come along and change the value later in the program.

  • Avoid Global Variables: Never declare a modifiable global variable. The only global named values in your code should be constants. Instead of making a value global, pass it as a parameter and/or return it as needed.
// bad style
int count;  // global variable!

void func1() {
    count = 42;
}

void func2() {
    count++;
}

int main() {
    func1();
    func2();
}
// good style
int func1() {
    return 42;
}

int func2(int count) {
    return count + 1;
}

int main() {
    int count = func1();
    count = func2(count);
}

Core C Statements

  • for vs. while: Use a for loop when the number of repetitions is known (definite); use a while loop when the number of repetitions is unknown (indefinite).
// repeat exactly 'size' times
for (int i = 0; i < size; i++) {
    ...
}

// repeat until the end of a linked list
while (node->next != NULL) {
    ...
}
  • {} And Control Statements: When using control statements like if/else, for, while, etc., always include {} and proper line breaks, even if the body of the control statement is only a single line.
// bad style
if (size == 0) printf("not ok\n");
else
    for (int i = 0; i < 10; i++) printf("ok\n");
// good style
if (size == 0) {
    printf("not ok\n");
} else {
    for (int i = 0; i < 10; i++) {
        printf("ok\n");
    }
}
  • if/else Patterns: When using if/else statements, properly choose between various if and else patterns depending on whether the conditions are related to each other. Avoid redundant or unnecessary if tests.
// bad style
if (degreesF >= 80) {
    printf("It's hot outside.");
}
if (degreesF >= 60 && degreesF < 80) {
    printf("It's nice outside.");
}
if (degreesF >= 50 && degreesF < 60) {
    printf("It's cool outside.");
}
// good style
if (degreesF >= 80) {
    printf("It's hot outside.");
} else if (degreesF >= 60) {
    printf("It's nice outside.");
} else if (degreesF >= 50) {
    printf("It's cool outside.");
}
  • Returning Booleans: If you have an if/else statement that returns a bool value based on a test, just directly return the test's result instead.
// bad style
if (score1 == score2) {
    return true;
}
return false;
// good style
return score1 == score2;
  • Testing Booleans: Don't test whether a bool value is == or != to true or false.
// bad style
if (x == true) {
    ...
} else if (x != true) {
    ...
}
// good style
if (x) {
    ...
} else {
    ...
}
  • Clean Syntax: Use the most clean, direct, conventional syntax available to you, e.g. ptr->field instead of (*ptr).field. Similarly, be thoughtful/consistent in use of array subscripts vs. pointer arithmetic. It's more common to use subscripts when accessing an individual array element, and more common to use pointer arithmetic when accessing a subarray. Avoid unnecessary use of obscure constructs, such as the comma operator, unions, etc. Use standard language features appropriately, for instance the bool type from stdbool.h, const for read-only pointers, etc.

  • Appropriate Pointer Usage:

    • no unnecessary levels of indirection in variable/parameter declarations
    • uses specific pointee type whenever possible, void* only where required
    • low-level pointer manipulation/raw memory operators used only when required
    • allocation uses appropriate storage (stack versus heap, based on requirements)
    • allocations are of appropriate size
    • use typecasts only and exactly where necessary and appropriate

Redundancy

  • Minimize Redundant Code: If you repeat the same code two or more times, find a way to remove the redundant code so that it appears only once. For example, place it into a helper function that is called from both places. If the repeated code is nearly but not entirely the same, try making your helper function accept a parameter to represent the differing part.
// bad style
foo();
x = 10;
y++;
...

foo();
x = 15;
y++;
// good style
helper(10, &x);
helper(15, &x);
...

void helper(int newX, int *x) {
    foo();
    *x = newX;
    y++;
}
  • if/else Factoring: Move common code out of if/else statements so that it is not repeated.
// bad style
if (x < y) {
    foo();
    x++;
    printf("hi");
} else {
    foo();
    y++;
    printf("hi");
}
// good style
foo();
if (x < y) {
    x++;
} else {
    y++;
}
printf("hi");
  • Function Structure: If you have a single function that is very long, break it apart into smaller sub-functions. The definition of "very long" is vague, but let's say a function longer than 40-50 lines is pushing it. If you try to describe the function's purpose and find yourself using the word "and" a lot, that probably means the function does too many things and should be split into sub-functions.

Efficiency

  • Save expensive call results in a variable: If you are calling an expensive function and using its result multiple times, save that result in a variable rather than having to call the function multiple times.
// bad style
for (int i = 0; i < strlen(str); i++) {
    ...
}

str[strlen(str)] = '\0';
if (strlen(str) > 10) {
    ...
}
// good style
int stringLength = strlen(str);
for (int i = 0; i < stringLength; i++) {
    ...
}

str[stringLength] = '\0';
if (stringLength > 10) {
    ...
}
  • Avoid making copies of data: when possible, avoid making unneeded copies of data in your programs.

Comments

The purpose of commenting your code is to make it easy to read and understand. Programmers spend significantly more time reading than writing code, and code you write only once can be read many times by many people. It is therefore important to prioritize readability throughout the code-writing process.

The Comment Hierarchy is a structure for doing just this. Each decreasing rung in the hierarchy becomes more specific to the implementation. From top to bottom (examples are from lecture 7's pig_latin_soln.c):

  • 1: File headers: At the top of the hierarchy are file-level headers. These should give the reader the highest-level understanding of what is contained in the file. You should place a descriptive comment heading on the top of every file to describe that file's purpose, along with your name and the course name. As an example, if the file is a collection of utility functions, it could describe the general category of the utilities and where they might be used. If it’s designed to be run (i.e. has a main()), then it can briefly describe the program behavior. Assume that the reader of your comments is an intelligent programmer but not someone who has seen this assignment before. This comment should not describe great detail about how it is implemented. Do not mention language-specific details like the fact that the code uses an if/else statement, that a function declares an array, that a function loops over a list and counts various elements, etc.
 1 /* CS107 Lecture 7
 2  * Code by Nick Troccoli and Lisa Yan
 3  * 
 4  * This program converts text to pig latin as an example of
 5  * how to use dynamic memory allocation on the heap with malloc,
 6  * realloc and free.
 7  *
 8  * If there are no additional command-line arguments, the program
 9  * prints out tests of converting individual words to pig latin.
10  * If there are additional command-line arguments, the program
11  * concatenates the pig latin versions of them and prints out the
12  * resulting string.
13  */
14 
15 #include <stdlib.h>
16 #include <stdio.h>
17
18 // ...rest of file...
  • Side note - citing sources: If you look at any resources that help you create your program (a book, lecture slides, section example, web page, another person's advice, etc.), you should list all of them in your comments at the start of the file. When in doubt about whether to cite a source, be liberal and cite it. It is important to cite all relevant sources. See our collaboration policy for more details.

  • 2: Function headers: Function headers are second in the hierarchy, and should be placed on each function in your file to describe each function's behavior. In addition to describing the high-level functionality of the function, these should describe the inputs and outputs to the function, and critically, any assumptions the function makes about those parameters. Specifically, if your function accepts parameters, briefly describe their purpose and meaning. If your function returns a value, briefly describe what it returns. If your function makes any assumptions, such as assuming that parameters will have certain values, mention this in your comments. It should also describe the function’s response to errors, if relevant. It should not describe great detail about how it is implemented. Do not mention language-specific details like the fact that the function uses an if/else statement, that the function declares an array, that the function loops over a list and counts various elements, etc. Note that for the main function, a comment above main is not always needed, since it may be a duplicate of the file header comment for the program. But it could be appropriate for some programs to talk about its behavior in slightly more detail than the program header comment might. Try to make the best decision you can for your program for whether a main comment is helpful for the reader or redundant.

27 /* Function: pig_latin
28  * --------------------------
29  * This function returns a pig-latinified version of the in string.
30  * It is the caller's responsibility to free the returned string,
31  * which is allocated on the heap.
32  * Simplified pig latin rules are:
33  * - if the word starts with a vowel, append "way"
34  * - otherwise, move all initial consonants to the end, and append
35  *   "ay"
36  *
37  * This function assumes that the provided word is lowercase.
38  * If the word begins with non-alphabetic characters, this function
39  * returns NULL.
40  */
41 char *pig_latin(const char *in) {
42     // If the word starts with non-alphabetic characters, we can't translate
43     if (strcspn(in, LOWERCASE_ALPHABET) > 0) {
44         return NULL;
45     }
46
47     // ...rest of function...
  • 3: Block comments: Third are block comments - code blocks, e.g. if statements, for loops, or just groups of related lines, should be commented if it is unclear from just reading the code what is happening or if they are sufficiently lengthy. These comments should describe the logical flow of whatever part of the function the block contains. They do not describe the mechanics of the operations but instead provide insight into what the result of the operations means and how it is used.
41 char *pig_latin(const char *in) {
42     // If the word starts with non-alphabetic characters, we can't translate
43     if (strcspn(in, LOWERCASE_ALPHABET) > 0) {
44         return NULL;
45     }
46 
47     char *out = NULL;
48 
49     // If the word starts with a vowel, add "way"
50     if (strchr(LOWERCASE_VOWELS, in[0]) != NULL) {
51         int out_len = strlen(in) + strlen("way");
52         out = malloc(sizeof(char) * (out_len + 1)); // +1 for null terminator
53         assert(out != NULL);
54 
55         pig_way(out, in);
56     } else {
57         // Otherwise, move all initial consonants to the end, and append "ay"
58         int out_len = strlen(in) + strlen("ay");
59         out = malloc(sizeof(char) * (out_len + 1));
60         assert(out != NULL);
61 
62         // ...rest of function...
  • 4: Line comments: Lastly, line-level comments are for explaining the mechanics of dense operations on a given line or two. It is much easier to read English than it is to read complex bit operations or arithmetic. (Note, however, that very dense lines are generally a sign that you can make your code simpler or at least break it into multiple lines.)
52         out = malloc(sizeof(char) * (out_len + 1)); // +1 for null terminator
  • Redundancy: Don't repeat what is already said by the code; instead, if you add comments, add additional detail or explanation.

  • Wording: Your comment headers should be written in complete sentences, and should be written in your own words, not copied from other sources (such as copied verbatim from the homework spec document).

  • TODOs: You should remove any // TODO: comments from a program before turning it in.

  • Commented-out code: It is considered bad style to turn in a program with chunks of code "commented out". It's fine to comment out code as you are working on a program, but if the program is done and such code is not needed, just remove it. You should also remove other dead or unused code.

Functions and Procedural Design

  • Designing a good function: A well-designed function exhibits properties such as the following:

    • Fully performs a single coherent task.
    • Does not do too large a share of the work.
    • Is not unnecessarily connected to other functions.
    • Stores data at the narrowest scope possible.
    • Helps indicate and subdivide the structure of the overall program.
    • Helps remove redundancy that would otherwise be present in the overall program.
    • Interface (parameters, return value) is clean and well-encapsulated.
  • Returning vs parameters: When possible, favor returning a result from a function instead of using a pointer parameter to pass back data.

// bad style
void max(int a, int b, int *result) {
    if (a > b) {
        *result = a;
    } else {
        *result = b;
    }
}
// good style
int max(int a, int b) {
    if (a > b) {
        return a;
    }
    return b;
}
  • const parameters: If you are passing a pointer to a function and your code will not modify the data it points to, pass it as a const pointer. For example, if you will not modify the contents of a string, pass it as a const char * instead of a char *.

  • Reimplementation: avoid reimplementing the functionality of standard library functions; instead, use the provided functions where possible (e.g. string manipulation, type converstion, etc.).

  • Appropriate data structures: choose appropriate data structures and types for your program data.

  • Avoid "chaining" calls: Chaining calls is where many functions call each other in a chain without ever returning to main. Make sure that main is a concise summary of your overall program. Here is a rough diagram of call flow with and without chaining:

// bad style
main
|
+-- function1
    |
    +-- function2
        |
        +-- function3
            |
            +-- function4
            |
            +-- function5
                |
                +-- function6
// good style
main
|
+-- function1
|
+-- function2
|   |
|   +-- function3
|       |
|       +-- function4
|
+-- function5
|   |
|   +-- function6
  • Dead Code: delete any "dead code", meaning code that is never executed and cannot be reached. For instance, code after a return statement that always executes will never be reached.
// bad style
void doSomething() {
    ...
    return;

    // Dead code!  Never executed.
    int x = 2;
}
// good style
void doSomething() {
    ...
    return;
}