CS 208 Coding Style Guide

Solving a coding challenge is often only half the battle; it is at least as important to write easy-to-read and maintable code.

The grading rubric for each of your programming assignments in this course will include a “code quality” item. Code quality covers a lot of ground, but a big part of it will be organization (e.g., appropriate use of functions, structs, etc. to help your reader think about the code) and style (e.g., quality of the names you choose, consistency of indentation, etc.).

This style guide details how you can make your C code more readable.

0. Audience

As with any form of writing, you will write more clearly if you understand your audience. For software source code, you should usually assume an intermediate-level understanding of the programming language you’re using. In the case of this course and the C language, your target audience should be, say, a CS major who has already taken this class rather than a C beginner (which you likely are at the start of this term!).

1. Documentation

Good code should be mostly self-documenting: your variable names and function calls should generally make it clear what you are doing. Comments should not describe what the code does, but why; what the code does should usually be self-evident.

There are several important types of comments:

  • File header: Each file should contain a comment describing the purpose of the file and how it fits into the larger project. For this class, the file header should also include the names of the code’s authors.

  • Function header: Each function should be prefaced with a brief comment describing the purpose of the function. This comment should describe the relationship between the function’s arguments and return value, any error cases that are relevant to the caller, any pertinent side effects, and any assumptions that the funciton makes.

  • Large blocks of code: If a function is particularly long, you can provide brief (typically one-line) comments in front of each conceptually coherent section of code to help the reader understand its purpose.

  • Tricky code: If there’s no way to make a bit of code self-evident, then it is acceptable to describe what it does with a comment.

2. Whitespace

Every time you open a block of code (a function, if statement, for or while loop, etc.), you should indent one additional level.

People can get very passionate about their feelings regarding spaces versus tabs; I personally use four spaces as indentations. You are free to use your own indentation style, but you must be consistent: if you use four spaces as an indent in some places, you should not use a tab elsewhere.

Whatever you choose, make sure not to mix tabs and spaces in the same file or even the same project. If you want to change the settings in VS Code, you should be able to find them in File -> Preference -> Settings, then search for “spaces”.

3. Line Length

Similar to tabs vs. spaces, there are also some strong opinions about line length. In the past (and even still today), some people expect 80-character limits, as CRT terminals were limited to 80 characters in width.

In general, you should try to avoid lines longer than 120 characters, or you risk issues with readability. If you use tabs instead of spaces, please limit your tab size to 4 characters in width.

To quickly check that lines in a file hello.c do not exceed 120 characters, run the following command in a terminal:

wc -L hello.c

4. Names

A variable or parameter name should describe the value it stores. Conciseness is good, but clarity is better. For example, you could have a personal convention of using the letter n to meant number of (e.g., n_employees would mean the number of employees). However, the more clear employee_count or number_of_employees would be better.

Do not be afraid of full words in names. In an era of auto-completing text editors, the slight benefit of brevity is more than offset by the increase in readability you get from using full words.

Function names should, for the most part, be imperative verb phrases like print_summary, push, set_time, etc. One major exception is functions that are accessor-like (though note that accessors aren’t a thing in C, given that classes are not a thing). For example, you might have a function to compute the area of a circle struct; such a function can be named either get_area or just area.

Trying to decide between one function name and another? Try writing a loop or conditional that involves a call of your function. Which name makes more sense at the point of calling? Choose that one.

Multiple-word names should be formatted consistently. For this class, aim to use underscore styling (e.g., get_area) rather than camel-case styling (e.g., getArea).

5. Magic Numbers

Magic numbers are numbers in your code that have more meaning than simply their own values. For example, if you are reading data into a buffer by doing fgets(stdin, buffer, 256), then 256 is a magic number because it represents the length of your buffer. On the other hand, if you were coutning by even numbers by doing for (int j = 0; j < 100; j += 2), then 2 isn’t really a magic number because it simply means that you are counting by 2s, but 100 is a magic number, because it represents the maximum value to which we’re counting.

You should use #define to clarify the meaning of magic numbers. In the above example, this means having a line #define BUFFER_LENGTH 256 at the top of the file, and then using the BUFFER_LENGTH constant in both the declartion of buffer and the call to fgets.

6. No Dead Code

Dead code is code that is not executed when your program runs, either under normal or exceptional circumstances. These include printf statements you used for debugging purposes but have since commented out. Your submitted programs should contain no dead code.

7. Modularity

You should try to make your code modular. On a low level, this means that you should not needlessly repeat blocks of code if they can be abstracted into a function. On a high level, this means that code that performs different functions should be separated into different modules.

For example, if your code requires a hash table, the code to manipulate the hash table should be separate from the code that uses the hash table, and should be accessed only through a few well-chosen public interfaces.

8. Failure Conditions / Error Checking

When writing a program, it is easy to only consider the success case (the “happy path”). However, it is essential to also consider failure cases. Many things can fail in your programs! Here are few examples:

  • The user’s input might not match your expected format.
  • A call to malloc might return NULL.
  • The filename the user gave you might not exist.
  • The user might not have permission to read the file they specified.
  • The disk might fill up.
  • The network host you were talking to might be down.

You should always thinking about what your program can do to resolve these errors, and how (or whether) it should report unresolvable errors to the user. If you do decide to print error messages, you should:

  1. write the messages as clearly as possible, and
  2. print them to standard error (i.e., fprintf(stderr, ...)) rather than to standard output.

In general, standard output (stdout) should be used for expected output data, and standard error (stderr) should be used for reporting exceptional situations. This way, for example, even if a person redirects your program’s output to a file or a pipe, the error messages will still appear in the terminal.

Network servers and operating systems need to be particularly robust. No matter what one client (or process) tries to do, your server (or kernel) should never crash. Error handling is more difficult in such cases, as you need to convert what is a “fatal error” from a client’s perspective into something that won’t actually kill the server process.

9. Memory and File Handling

If you allocate memory (e.g., using malloc or calloc), you should free it after use. Your program should not have any memory leaks.

If you open a file (e.g., using fopen), you should close it when you don’t need it anymore. Closing files is essential in many contexts. For example, C’s output functions buffer output for efficiency’s sake, and thus if your program terminates before the output file is closed, buffered data might never be written to the file.

10. Consistency

This style guide purposefully leaves many choices up to you (for example, where the curly braces go, whether one-line if statements need braces, how far to indent each level). It is important that, whatever choices you make, you remain consistent about them. Nothing is more distracting to someone reading your code than random style changes.


This style guide was adapted from Jeff Ondich’s adaptation of Aaron Bauer’s adaptation of CMU’s 15-213 style guide.