Formatting
formatting::brace-styles
- The brace styles must be consistent across all files. A one-line loop or conditional should use braces.
- Don’t do this:
// confusing, hard to follow
while (x < y) {
if (x != 0) { binky(x);
}
else { winky(y);
y--; }}
return x;
- Do this:
// indentation follows structure
while (x < y) {
if (x != 0) {
binky(x);
} else {
winky(y);
y--;
}
}
return x;
formatting::indendentation
- Files must be indented correctly to indicate to the reader where structures start and end. Using a mix of tabs and spaces does not transfer well to other team members’ computers.
formatting::line-length
- All lines in the file must be less than than 90 characters.
- This makes code easier to read regardless of the screen/monitor size.
- When any line is longer than 90 characters, break it into two lines. Indent the overflow text to align with text above.
formatting::one-liners
- Code should be broken down into multiple lines, with one statement per line.
- Control loops and statements should be expanded into multiple lines instead of having their braces in one line.
- Exception: An if statement with only one statement (i.e. one semicolon) can be used in one line as long as you are following formatting::line-length above and use braces.
- With some exceptions (like for loops), there should only be one semicolon per line.
formatting::spacing
- A block of code must have a space after a keyword (e.g.,
if
,else
,while
, …) - There should not be extraneous white-space or not enough white-space
- Use white-space between operators and their operands. Add parentheses to show grouping where precedence might be unclear to the reader.
- Use blank lines to separate functions and logical groups of statements within a function.
Variables
variables::casting-malloc
- Casting the result of
malloc()
is redundant, as it will be implicitly casted to the data type of the variable that it’s assigned to.
variables::declaration-location
- Declare variables in the narrowest possible scope, as close to its usage as possible.
- Ex., if a variable is used only inside a loop, declare it inside the scope for the loop body rather than at the top of the function or at the top of the file.
- Declaring a variable in an inner scope with the same name as a variable in the outer scope will cause the inner use of name to “shadow” the outer definition. Not only is this confusing, but it often leads to difficult bugs.
variables:declaration-order
- Variables and functions should generally be declared in the following order:
- global constants
- struct definitions
- functions
variables::description
- A variable is properly descriptive and not overly descriptive
- Use nouns for variable names and verbs for function names
- Ex. Use
first_elem
instead offirst_element_in_array
variables::global-variables
- Do not use global variables besides constants. When there is information to be shared across function calls, it should flow into and out via parameters and return values, not reach out and access global state.
variables::magic-constants
- Use a
const
variable to appropriately abstract magic constants from the code- Why? Magic constants can make it hard to understand the purpose of code. They (especially when appearing multiple times) are very difficult to tweak (e.g., for a faster machine).
- Constants should be declared using
SHOUTING_CASE
- This doesn’t just apply to numbers. For example, file paths are examples of variables that should (most of the time) be constants.
- All constants should be defined globally at the top of the file to make it easier to modify and manage them in one place.
variables::snake-case
- Variable should be named with
snake_case
variables::type
- Use the
bool
type for booleans, not theint
type - Use a variable type that specifies the size explicitly (e.g.,
int32_t
,size_t
) rather than anint
orlong
- Ex. You should use of
size_t
instead ofint
when compared tostrlen
, or anint
cast to asize_t
- Why? You should use
size_t
instead ofint
for any variables that will compare withsize_t
’s. The reason we usesize_t
is that it’s platform independent;size_t
will always be a valid index of something regardless of platform, whereasint
might not be the right size/number of bits.
- Ex. You should use of
Macros
macros::defines
- Uses constant variables instead of defines.
- Defines are used for text substitution and unlike consts, they are not typed checked, which could lead to unexpected issues in the code.
macros::misuse
- Do not misuse or abuse a macro (We expect almost all usages of function macros to fit this definition. You have been warned.)
- A macro in C is a named text substitution that is performed by the C preprocessor. Macros can be used to define constants, function-like constructs, and reusable code snippets.
Functions and Decomposition
functions::code-duplication-multiple-functions
- Decompose significant amount of code duplication (this is usually any more than ~10 lines) by factoring out common code into helper functions.
functions::code-duplication-single-function
- Lines of code should not be duplicated within a single function.
- In these cases, a
do while
loop may be helpful.
functions::description
- A function name should not be overly descriptive
- Ex. Use
append()
instead ofadd_element_to_end_of_array()
- Ex. Use
functions::error-handling
- A function should not exit the program on a potentially recoverable error (an alternative is to return from a helper function with an error code)
- Ex. You should return -1 from a
list_index()
function to show that the element was not found.
- Ex. You should return -1 from a
- Make sure to check the return value of a function that could fail
- Handle errors in a reasonable way
- Ex.
malloc
should be checked with an assert - Why? It is important to check in the code that all memory allocations succeed. This is called “fail-fast” behavior. Bugs are harder to track down if you let the program keep running past where the error occurs.
- Ex.
functions::expensive-function-calls
- Function calls (especially for expensive ones) should be minimized while still preserving the functionality of the code.
- If a function returns a value that can be reused/slightly modified, it should be saved into a variable rather than called multiple times.
functions::library-improper-usage
- Library functions should be called properly according to their function definitions.
- Ex.
sdl_init()
should be called inemscripten_init
(which only gets called once inemscripten.c
) rather than inemscripten_main
(which gets called every frame).
- Ex.
functions::library-reimplementation
- Functionality that is already provided by library functions should not be reimplemented.
- Ex. Rather than going through every body in a scene and rendering it, you should just use
sdl_render_scene()
.
- Ex. Rather than going through every body in a scene and rendering it, you should just use
functions::over-modularization
- While code should be decomposed into helper methods, it shouldn’t be taken to the extreme. This ends up making the code harder to read and trace. Ex. A line of code that is only written once is factored into a helper method. Instead, a concise comment describing what the line does (if it’s even necessary) would be more helpful.
functions::snake-case
- A function should be named with
snake_case
Modules and Types
types::header-file
- Make sure that all functions outlined by the header file (e.g., it has a public interface) are implemented in the C file
types::include-guards
- A header file should not misuse include guards
types::snake-case
- A type definition should end with
_t
and be insnake_case
Commenting
commenting::description
- A comment should describe the actual behavior that the piece of code has
- A comment should not be unclear, useless, or uninformative
- All public functions should be docmented ONLY in the header file, NOT in the
.c
file!- Make sure to specify the type and purpose of each parameter and return value, as well as what the function does at a high level.
- Code should not be “overcommented”
- Ex: Are there descriptive comments in the C files that are already present in the H files?
- Why? Don’t need to leave the same comments twice. It makes them harder to update.
Compilation/Functionality
compilation::errors
- Code should not have memory leaks
- Code should not undefined behavior (e.g., signed integer overflow, illegal memory accesses)
compilation::tests
- Code should pass the provided tests
compilation::warnings
- No warnings should be generated when the code is compiled
- Make sure to resolve all warnings locally, as the tests will not pass on gitlab with warnings
Design
design::booleans
- Do not compare boolean expressions to
true
/false
- Boolean expressions are prone to redundant/awkward constructions. Prefer concise and direct alternatives. A boolean value is
true
orfalse
, you do not need to further compare totrue
/false
or convert a boolean expression totrue
/false
. - Don’t do this:
// confusing, hard to follow
int abs(int x) {
if (negative(x) == true){
return -x;
}
else {
return x;
}
}
- Do this:
// indentation follows structure
int abs(int x) {
if (negative(x)) {
return -x;
}
else {
return x;
}
}
design::dead-code
- A block of dead code or block of commented out code is left in the final submission
- It is bad practice to submit a program with large chunks of code “commented out”. You may comment out code as you work on a program, but if the program is done and such code is not needed, remove it.
design::debug-statements
- Code should not have debug statements (e.g., printfs or commented out code) that should be deleted
design::encapsulation
- Code should be encapsulated
- Encapsulation is, at a high level, hiding data from clients of structures in the
.c
files. It is the process of hiding the implementation of a module from the code that uses it. This is important because if we need to change our underlying implementation, code that we have written that uses it should still be able to function. Encapsulation also makes things more readable and understandable for the user. - Ex: Implementing structs in
.h
files, and directly accessing them in other.c
files. - Why? Our more advanced structs should have their implementations closed, and should have getter and setter methods to interact with them.
- Encapsulation is, at a high level, hiding data from clients of structures in the
design::extra-control-flow
- Additional control flow should not be used if they can be simplified.
- Ex.
if (...){...return;}
does not need an else statement if it returns no matter what. - Ex.
else{ if (...){...} }
can be replaced byelse if {}
- Ex.
design::includes
- Code should not include a “
.c
” file rather than a header- Why? This is bad because…well…it’s not acceptable C practice.
#include
literally copy/pastes the file; if you#include
a C file, bad things can happen. You should include.h
files and compile all the.c
files together to solve this problem.
- Why? This is bad because…well…it’s not acceptable C practice.
design::loop-choice
- Use a
for
loop when the number of repetitions is known (definite); use awhile
loop when the number of repetitions is unknown (indefinite).
design::single-owner
- Every pointer that is malloced for should be freed exactly once
Acknowledgements
Thanks to Stanford and CMU as sources for similar style guidlines.