The following is a list of code quality patters that we will check for. Each item includes the week in which you will start being graded on them.
Formatting
formatting::brace-styles
[activated week 4]
- 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;
- Don’t do this:
formatting::indendentation
[activated week 4]
- 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
[activated week 4]
- 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
[activated week 2]
- 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. 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
[activated week 2]
- 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
[activated week 3]
- 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::constant-scope
[activated week 3]
- 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::declaration-location
[activated week 2]
- 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, it often leads to difficult bugs.
variables::description
[activated week 2]
- A variable is properly descriptive and not overly descriptive
- Use nouns for variable names and verbs for function names
- Ex. Don’t do this
first_element_in_array
vs. do this:first_elem
variables::global-variables
[activated week 3]
- 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
[activated week 2]
- 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.
variables::snake-case
[activated week 2]
- Variable should be named with
snake_case
variables::type
[activated week 2]
- 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 of
int
when compared tostrlen
, or anint
cast to asize_t
- Why? You should use
size_t
instead of int 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, whereas int might not be the right size/number of bits.
- Ex. You should use of size_t instead of
Macros
Starting from week 3, using macros will almost certainly result in a 0 for the assignment.
macros::defines
[activated week 3]
- 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
[activated week 3]
- 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
[activated week 2]
- Decompose significant amount of code duplication, by factoring out common code from into helper functions. (this is usually any more than ~10 lines) and or lack of decomposition of functions into helper functions
functions::code-duplication-single-function
[activated week 2]
- Lines of code should not be duplicated within a single function.
- In these cases, a
do while
loop may be helpful.
functions::description
[activated week 2]
- A function name should not be overly descriptive
- Ex. don’t do this:
add_element_to_end_of_array()
vs. do this:append()
- Ex. don’t do this:
functions::error-handling
[activated week 2]
- 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
[activated week 2]
- 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::over-modularization
[activated week 3]
- 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 would be more helpful.
functions::snake-case
[activated week 2]
- A function should be named with
snake_case
Modules and Types
types::header-file
[activated week 2]
- 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
[activated week 2]
- A header file should not misuse include guards
types::snake-case
[activated week 2]
- A type definition should end with
_t
and be insnake_case
Commenting
commenting::description
[activated week 2]
- 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
[activated week 2]
- Code should not have memory leaks
- Code should not undefined behavior (e.g., signed integer overflow, illegal memory accesses)
compilation::tests
[activated week 2]
- Code should pass the provided tests
compilation::warnings
[activated week 2]
- 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
[activated week 4]
- 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; } }
- Boolean expressions are prone to redundant/awkward constructions. Prefer concise and direct alternatives. A boolean value is
design::dead-code
[activated week 2]
- 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
[activated week 2]
- Code should not have debug statements (e.g., printfs or commented out code) that should be deleted
design::encapsulation
[activated week 2]
- 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
[activated week 2]
- 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
[activated week 2]
- 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
[activated week 2]
- 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
[activated week 2]
- Every pointer that is malloced for should be freed exactly once
Acknowledgements
Thanks to Stanford and CMU as sources for similar style guidlines.