Style Guide

Written by by Nick Troccoli, based on documents by Marty Stepp and Julie Zelenski

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();
  • 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, declare it as a constant using const or #DEFINE, 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", where a number is hardcoded into your program.
const int MAX_RESPONSE_LENGTH = 100;
// or
#define MAX_RESPONSE_LENGTH 100;
  • 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) return;
else
    for (int i = 0; i < 10; i++) printf("ok\n");
// good style
if (size == 0) {
    return;
} 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;
} else {
    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

  • Class header: Place a descriptive comment heading on the top of every file describing that file's purpose. Assume that the reader of your comments is an intelligent programmer but not someone who has seen this assignment before. Your comment header should include at least your name, course, and a brief description of the assignment. If the assignment asks you to submit multiple files, each file's comment header should describe that file/class and its main purpose in the program.

  • 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.

  • Function headers: Place a comment heading on each function of your file. The heading should describe the function's behavior. 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.

  • Inline comments: Inside the interiors of your various functions, if you have sections of code that are lengthy or complex, place a small amount of inline comments near these lines of complex code describing what they are doing.

  • Implementation details: Comment headers at the top of a function or file should describe the function's behavior, but not 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.

  • 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.

Commenting Example

/* FUNCTION: bubble_sort_int
 * -------------------------
 * Parameters:
 *     arr - a pointer to an array of integers to sort.
 *     n   - the length of the provided integer array
 * 
 * Returns: NA
 *
 * This function sorts an array of integers in-place into ascending
 * order using the bubble sort algorithm, which continually traverses 
 * the array, swapping pairs of numbers that are out of order 
 * until no more pairs need to be swapped.
 */
void bubble_sort_int(int *arr, int n) {
    while (true) {
        bool swapped = false;

        // Go through each pair of elements, swapping if needed
        for (int i = 1; i < n; i++) {
            if (arr[i-1] > arr[i]) {
                swapped = true;
                swap_int(&arr[i-1], &arr[i]);
            }
        }

        if (!swapped) {
            return;
        }
    }
}

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;
    } else {
        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;
}