CSC230 Coding Style

Coding style is important because it increases readability and maintainability of your code. Most companies will have some form of style expectations for their employees, and this class supports this by defining our own style guidelines for student work. Following the guidelines will make it easier for the teaching staff to read and help diagnose problems with your code, without having to spend extra time figuring out your formatting.

Student code is expected to follow this guide for the programming projects. You'll probably want to follow the same style for all your C code, but style rules are not checked for the smaller programming exercises.

The Coding Style Guidelines for CSC230 are adapted from the Linux Kernel's Coding Style document. Since we'll be covering more and more of the language as the semester progresses, some of these guidelines will make more sense after we get to the relevant part of the language.

Comments

You are encourged to include comments within the body of your functions. Particularly for long sections of code, this can help to explain what you're trying to do to another developer or to yourself in the future. For the programming projects, the commenting part of your grade will reflect your comments in a few particularly important places.

File comments

Every source file (.c, .h) will contain a javadoc-style block comment at the top which includes the following:

Function comments

Function declarations or definitions will be preceded by a javadoc-style block comment. This comment should only appear in one place in the source code; you don't want to duplicate comments if you can avoid it. Where do you put the comment? That depends on whether the function is expected to be used only within a single component or if it is intended for use outside the component.

If the function is expected to be used outside the component where it is defined, then it will have a prototype in the header file for that component. That's where you should put the block comment. This policy makes sense. Other client code will need access to the header in order to use the function. Putting the block comment there will also make the function's documentation available to programmers who need to use the function. If a function is intended for use only within a single component, then it should be marked as static and it should not have a prototype in the header. For these internal functions, their block comment should be given right above the function definition.

A function's block comment should contain:

Constant and Global variable comments

Preprocessor-defined constants and file-level (e.g., global) variables should be proceeded by a javadoc-style block comment describing their purpose. These don't require specific tags, just a short description of what the variable or constant is for. Be sure to see the restriction on global variables below.

Type definitions

Definition of new types (e.g., structs or typedefs) should be preceded by a javadoc-style block comment describing their purpose. No particular tags are required here; just a short description of what the type represents.

Code Organization

Each statement should start on a new line; don't put multiple statements on the same line. This includes statements in the body of an if statement, a for loop or a while loops. For longer statements, you may have to split them up across multiple lines in order to make the code easier to read.

For if statements, for loops and while loops, if the body is just one statement you are not required to put curly brackets around it. You may still put curly brackets here, if you want. It's probably a good idea. Either way, each statement in the body should start on a new line.

Indentation

All code indentation will be done with spaces. No hard tab characters may be used (except for Makefiles, where we have to use hard tabs). Why do we have this requirement? Hard tabs behave inconsistently from one editor to the next, so the nice, pretty indenting you did on one editor may not look as good when someone else views your file. Spaces generally behave the same across all editors.

You can choose how many spaces you'd like to indent with. Four-space indentation is common, but you can use 2-space if you'd like (or 3 or 5 or 8, just not zero). Whatever you use, be sure you're consistent. Don't mix 2-space indentation and 4-space indentation. Depending on the editor you use, you may be able to configure it to automatically perform the kind of indentation you want (that's what I do).

As usual, the indentation level will indicate how deeply nested the current section of code is. The indentation level will increase in the following circumstances:

Curly Braces

For function definitions, the opening curly brace will appear at the start of the line after the function header. For everything else (e.g., while or switch), the opening curly brace will be at end of the line containing the control structure. The sample program below shows several examples of this.

Line Termination

Line termination can be a little tricky, for two reasons. Different operating systems use different character codes to mark the ends of lines, and different editors behave differently in their treatment of the last line of a file.

In general, we'll expect all lines of text (including the last line) to end with Unix-style line termination, a single linefeed character (\n, hexadecimal 0x0A). If you've used a text editor on a Window machine, or if you cut-and-paste on a Windows machine it may insert different line termination sequences. Before submitting, you may want to use a tool like dos2unix to fix this. If your source file is named prog.c, running the following command on a Linux machine should change your line termination from Windows-style to Unix-style.

$ dos2unix prog.c

Sometimes, it's handy to be able to see exactly what's in a file. The hexdump program can help with this. The following command will show the sequence of characters in the source file, prog.c, in hexadecimal on the left and as symbols on the right where possible:

$ hexdump -C prog.c

Magic numbers

You shouldn't have magic numbers in your source code. Obvious values are generally OK, ones you might use to iterate over an array (like 0, 1, -1, maybe even 2). Rather than classifying particular values as being magic numbers, we try to consider what job the number is doing. When we're trying to decide if a value should be considered a magic number, we'll consider the following:

We will generally use the C preprocessor define named constants for values that have special meaning in our programs. As a name, choose something meaningful that helps explain what the value is for; don't use a word for the current value as the name (like the last example below).

    // All good
    #define PI 3.14159265
    #define BASE_SCORE 350
    #define BUFFER_SIZE 100

    // Bad
    #define FIVE 5

When terminating, the exit status should always be a named constant, either defined in the program itself or via a header file:

    // The standard library constant for unsuccessful execution.
    exit( EXIT_FAILURE );
    
    // Maybe some constant I defined for an error condition.
    exit( MEMORY_EXHAUSTED );

Design

Global Variables

In general, global variables should be avoided. Unless a project description expressly permits it, you're not allowed to define new global variables as part of your solution. Typically, this means that a function will need parameters to pass in any data it needs to work with, and it can return its results via the parameters or the return value.

Function Types and Behavior

Projects and exercises may specify the parameter types, return types and expected behavior for particular functions you're expected to implement. In your solution, you need to stick to types and behavior described in the assignment. We may test some of these functions individually, and your solution won't pass our tests if you implement them differently.

Function Visibility

When you're defining functions in a component, you should consider whether the function is for internal use, only to be used by other code in the component, or if it's provided for use by other code outside the component. This is like thinking about whether a Java method should be private or public. If a function is intended for use outside the component where it is defined, it should have prototype in the component's header. If it's intended for use only inside the component, it should not have a prototype in the header, and it should be marked as static so other code can't use it.

Const-ness

Array and pointer parameters to functions should be marked as const whenever possible, whenever the function doesn't need to modify the value that the parameter refers to. This makes the function more useful, since it lets the function be used with values that are either const or non-const. Likewise, a returned pointer should be marked as const if the caller should not modify the memory the pointer points to. Code should respect the const-ness of function parameters and return values. If you feel like you have to cast away const, you're probably doing something wrong.

Style Checking

We don't provide an automated style checker for this class. The style rules are simple enough that we're expecting students to learn the rules early in the semester and then just keep them in mind as they're writing code for the projects. A good text editor can help you follow some of the rules.

When we grade assignments, we'll just look at the source code to see if it follows what the guide says. Starting with project 2, we will use a Jenkins system to automatically run some tests on each submission. On the earlier projects, the Jenkins system will also check some parts of the style guide and a few other expectations for the project submission (e.g., working Makefile, all the right files submitted). As we move to the later projects, the Jenkins system won't do as much automated checking, and you'll need to check more of these things yourself.

Github Repo Housekeeping

You'll be submitting projects 2-6 using a github repo. A small part of your project grade will reflect repo management, whether or not your repo contains appropriate files for the project. Your repo should contain your source files, along with any other files needed to build your project (e.g., a makefile). Typically, your repo will also contain test inputs and outputs, testing scripts and possibly some test driver code. However, your repo shouldn't contain extraneous files like temporary output files, object files, executables or other files that could be re-built from the source. On projects 2-6, we'll let you know what should be in your repo. Missing some expected files or having extraneous files may cost you a small number of points.

Example

The following example is intended to be a good demonstration of our commenting and formatting requirements. In addition to helping to illustrate the style guidelines, this program will help detect line termination problems, if you give it another program's source code on standard input.

/** 
    @file check.c
    @author John Q Public (jqpublic)
    This program performs a few basic style checks on C source code
    read from standard input.  It looks for hard tab characters and for
    different types of line termination.
*/

#include <stdio.h>
#include <stdlib.h>

/** Exit code if style problems are encountered. */
#define STYLE_ERROR 255

/** True if the input contains windows-style line termination.
    (this is a global variable, something to avoid) */
int windowsNewlines = 0;

/** True if the input contains unix-style line termination. */
int unixNewlines = 0;

void detectNewline( int, int );

/** 
    Program starting point, reads characters from standard input until end-of-file.
    @param argc number of command-line arguments, not used.
    @param argv list of command-line arguments, not used.
    @return program exit status
*/
int main( int argc, char *argv[] )
{
    // Previous characters read, with EOF indicating no previous character.
    int prev = EOF;

    // Set to true if the file contains a tab.
    int containsTab = 0;

    // Normally, standard input will be in text mode, so we may not see
    // differences in line termination.  This should fix that.
    freopen( NULL, "rb", stdin );

    // Read all the input characters.
    for ( int ch = getchar(); ch != EOF; ch = getchar() ) {
        if ( ch == '\t' )
            containsTab = 1;
        
        detectNewline( prev, ch );

        prev = ch;
    }

    // Is there a style problem?
    if ( containsTab || windowsNewlines ) {
        if ( containsTab )
            printf( "This file contains hard tab characters\n" );

        if ( windowsNewlines ) {
            if ( unixNewlines )
                printf( "This file contains mixed types of line termination\n" );
            else
                printf( "This file contains windows line termination\n" );
        }

        return STYLE_ERROR;
    }

    // Did the last line have line termination?
    if ( prev != EOF && prev != '\n' )
        printf( "The last line of this file doesn't have line termination\n" );

    return EXIT_SUCCESS;
}

/**
   Given a two-character sequence, determine whether it looks like
   windows- or unix-style line termination.  Updates windowsNewlines
   or unixNewlines to indicate line termination encountered.
   
   @param prev previous character read, or EOF if there is no previous character.
   @param current most recent character read.
*/
void detectNewline( int prev, int current )
{
    if ( current == '\n' ) {
        if ( prev == '\r' )
            windowsNewlines = 1;
        else
            unixNewlines = 1;
    }
}