Coding Standards and Style Guide

In professional software development settings, organizations typically have a set of coding standards, which are rules you are expected to follow regarding your style. Groups enforce these standards in a variety of ways, including the absolute rejection of any piece of code that deviates from this style. Here are coding standards appropriate for CS 261. They are mostly derived from the GNU standards for writing C, which are the standards often used in open source software. I have made some adjustments to more closely match my own C style, which is essentially K&R style with mandatory block braces (often called 1TBS) and opening function braces on new lines.

If you already have your own preferred coding style that is clean and consistent, you may use it in this class. Any of the standard indentation styles are permissible. If you choose to use an alternate style, make sure you also convert the starter code to maintain consistency.

Ultimately, you must submit code that is readable, clean, and consistent. I will deduct points from your project grade if your code is unreadable, cluttered, or inconsistent. Many programmers have legitimate disagreement about which coding standards are best, but there is nearly universal agreement that code should be readable, clean, and consistent. If you are unsure of what qualifies as "readable, clean, and consistent," please ask on Piazza or just use the coding standards specified here.

To help ensure consistent formatting, you can use the astyle utility to reformat your code before you submit it (or even while you are working). This utility should be installed in all the labs and the UUG Linux VM. Here is a set of command line parameters that will enforce 1TBS, 4-space indentation, and 100-character-max lines:

astyle -A10s4cxC100S <source-file>

In addition, you are required to adhere to the C99 standard. You may not use C11 or C++ features, and you may not use older C features that were deprecated by C99. By default, the provided Makefiles include compiler switches that enforce this standard--if you do not modify the makefile you shouldn't have to worry about violating the standard.

Much of the content in this coding standard is based on material originally provided by Dr. Michael Kirkpatrick.

  • Comments - Use them. Generally speaking, every 5-10 lines of code should have a brief one-line comment like shown below unless it is obvious what the code does. You should take particular care to document any "magic numbers," odd hacks (especially involving pointers), or anything else that might not be immediately clear to a reader. You may use either C-style /* */ comments or C++-style // comments, but make sure whatever you use is clean and readable.
  • Indentation and alignment - Indent using four spaces per level of indentation with curly braces for all blocks, and only on the next line for functions. Examples:

    Do this: Don't do this
    int main (int argc, char **argv)
    {
        /* code here */
        return EXIT_SUCCESS;
    }
                    
    int main (int argc, char **argv) {
        /* code here */
        return EXIT_SUCCESS;
    }
                    
    for (i = 0; i < 10; i++) {
        printf ("%d\n", i);
    }
                    
    for (i = 0; i < 10; i++)
        printf ("%d\n", i);
                    
    for (i = 0; i < 10; i++) {
        printf ("%d\n", i);
    }
                    
    for (i = 0; i < 10; i++) {
            printf ("%d\n", i);
    }
                    
    if (i < 10) {
        printf ("%d\n", i);
    } else {
        printf ("Nope\n");
    }
                    
    if (i < 10)
        printf ("%d\n", i);
    else {
        printf ("Nope\n");
    }
                    

  • Line spacing - In general, there should be 1-2 empty lines between logically-disjoint sections of code, and this should be done consistently across all of the code:

    Do this: Don't do this
    initialize_code();
    initialize_data();
    initialize_rodata();

    while (!done) {
    done = run_program();
    }

    clean_up();
    initialize_code();

    initialize_data();
    initialize_rodata();




    while (!done) {
    done = run_program();
    }

    clean_up();

  • Naming conventions - When naming functions and variables, use all lower-case and use an underscore ('_') to separate words. Pick meaningful names and types, but keep simple things simple.

    Do this: Don't do this
    size_t bytes_read;
    char *message_read;
    for (char c = 'A'; c <= 'Z'; c++)
    int b;
    char *MyIncomingMessage; for (char current_character = 'A'; current_character <= 'Z'; current_character++)

  • No long lines - All lines of code should be no longer than 80 characters wide. Long lines of code should be broken and wrapped to the next line. The continued line should be indented in a natural alignment:

    Do this:
    some_variable = a_really_long_variable_name + another_variable +
                    yet_another_really_long_name
                
    int calculate_sum (int first_value, long second_value, int third_value,
                       unsigned int last_value)
    {
      /* ... */
    }
                

  • No long functions - Where possible, restrict functions to two screens (about 100 lines of code), and never have more than 3 levels of indentation. Code that is longer than this or uses more levels of indentation is typically too complex to maintain. There are a few exceptions that you will encounter in CS 261 where you must write a function that handles every possible Y86 opcode using a giant switch statement. Still, it is recommended that in these cases you write helper functions to handle as much of the actual work as possible to simplify the main case statement.
  • Always initialize local variables - If you do not provide an explicit initial value for local variables, the variable will contain an unpredictable value. This could cause very unexpected behavior, which may be different every time you run the program. If you don't know what to set it to, 0, false, or NULL tend to be good options. You might also consider using calloc instead of malloc to initialize heap-allocated memory to zero.
  • Safe pointer practices - After calling free() to free a pointer, immediate follow up by setting the pointer to NULL. After calling malloc() to get dynamically allocated data, validate the allocation was successful by checking that the pointer is not equal to NULL.
  • Safe input and output - Some functions in the C standard library are now considered to be "unsafe" because they are vulnerable to buffer overruns. For the purposes of this class, you may not use any of the unsafe functions. See the function quick reference for a list of unsafe functions and their alternatives.

Recommended guidelines

In addition to the requirements above, the following practices are used in the base source code distributions. Following these guidelines would help your code to match the existing style and make your code more readable.

  • When using malloc() to dynamically allocate an array, use sizeof() instead of making assumptions about data sizes:

    Do this: Don't do this
    size_t num_entries = 5;
    uint16_t *data =
        malloc (sizeof (uin16_t) * num_entries);
                
    uint16_t *data = malloc (10);
                

  • Short-circuit functions when checking for errors rather than using if-else blocks. This makes the error conditions explicit and keeps error-handling code close to where the error checking occurs.

    Do this: Don't do this
    if (value < 0) {
        fprintf(stderr, "bad value\n");
        return false;
    }
    
    /* lots of code here */
                
    if (value >= 0) {
    
        /* lots of code here */
    
    } else {
        fprintf(stderr, "bad value\n");
        return 1;
    }
                

  • Avoid global variables. Global variables should be thought of as a last resort, as the values can change from one function call to the next.
  • Use #define for constants as appropriate (and particularly avoid "magic numbers" that appear without explanation). The name of the constant should be in all upper-case:

    Do this: Don't do this
    #define QUESTION_COUNT 5
    for (int qid = 0; qid < QUESTION_COUNT; qid++)
    {
        /* stuff here */
    }
                
    for (int i = 0; i < 5; i++)
    {
        /* stuff here */
    }
                

Text Editor Setup

You may use any text editor, although on stu I recommend using VS Code, nano, vim, or emacs. For this semester, I strongly recommend using VS Code, and I have prepared two video tutorials about how to get it set up:

  1. VSCode + stu Tutorial
  2. VSCode + CS 261 Tutorial

If you choose to use the nano editor, I recommend the following command-line options to help you stick to the coding standards:

nano -AEim -T 4 -r 80

To prevent having to type all of that every time, you can put the following command in your ~/.bashrc file (and then use "nnano" to launch the editor):

alias nnano='nano -AEim -T 4 -r 80'

Remember that makefiles do require tabs, so if you are editing a makefile you'll want to use the original version.

If you choose to use the vim editor, I recommend putting the following in your ~/.vimrc file:

" use four spaces for each step of (auto)indent.
set shiftwidth=4

" hitting <Tab> will insert four spaces instead.
set tabstop=4
set softtabstop=4

" round indent to multiple of shiftwidth.
set shiftround

" use spaces instead of tabs to insert a tab.
set expandtab

" copy indent from current line when starting a new line
" (also deletes indents if nothing else is written on the line)
set autoindent

" wrap lines at 80 characters
set textwidth=80

" exception: use tabs in Makefiles
autocmd FileType make set noexpandtab shiftwidth=4 softtabstop=0

If you wish to use the emacs editor you are on your own, and may Saint IGNUcious have mercy on your soul.