CS 208 Coding Style Guide
Making your code easy to read makes it easier to maintain, easier to debug, and easier to get right in the first place than if you write impenetrable code. It's also a sign of professionalism as a programmer and politeness to the people who encounter your code in any context.
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 document describes several techniques for making your C code 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.
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 in to 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 function 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.
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.
Personally, I am an advocate of using spaces for indentation (go ahead, I dare you to search the internet for "tabs vs spaces"). But you absolutely positively should not use both in the same file or even the same project. Be careful about this if you work with a partner.
As far as I can tell, VS Code defaults to using spaces for indentation.
3. Line length
There's a lot of advice out in the world about how long lines of code should be. Some people insist on an 80-character limit per line, which hearkens back to the days when paper or CRT terminals were 80 characters wide. In this course, I do not have a line-length requirement. But generally, letting your lines get longer tha 120 characters is asking for readability trouble.
To quickly check that file.c
does not exceed 120 characters, run wc -L file.c
to see its max line length.
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 mean number of
(e.g. n_employees
would mean the number of employees). But probably employee_count
or even number_of_employees
is better.
Do not be afraid of full words in names. What benefit do you get from using buf
instead of buffer
or uname
instead of user_name"
In an era of autocompleting 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, since 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, please use underscore_style rather than camelStyle.
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)
, 256 is a magic number because it represents the length of your buffer. On the other hand, if you were counting by even numbers by doing for (int j = 0; j < 100; j += 2)
, 2 is not 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, doing #define BUFFER_LENGTH 256
and then using the BUFFER_LENGTH
constant in both the declaration 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 since commented. 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. However, it is essential to also consider failure cases. Many things can fail in your program: the user's input might not match your expected format, 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… the list goes on. You should always think about what your program can do to resolve these errors, or how (and 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 should be used for expected output data, and standard error 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 (malloc
, calloc
), you should free it after use. Your program should not have memory leaks. If you open a file, 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.
Adapted from Aaron Bauer's adaptation of CMU's 15-213 style guide.