⚠ This is for Spring 2019, not the current semester.
Code quality standards
In this class, you will learn to write good code. That takes a different kind of skill and effort than just making a small homework assignment work. However, good code is easier to read, debug, and maintain. It also helps you avoid creating bugs in the first place.
Summary
Note: Read the full explanations. This summary table omits many important details.
Rule: Functions that are never called from within a file and are not part of the public interface (i.e., required for homework) must be removed or commented out.
✗
int main() {
return EXIT_SUCCESS;
}
void _foo() { // not called by main(…) or anything else
// ...
}
Reason: Unused functions (including old versions) cause confusion. You may think you're debugging the one that gets called and the realize you're debugging something that never gets called at all.
Rule: Declare and initialize for loop counter in the for statement.
✓
for(int i = 0; i < 5; i++) {
printf("%d\n", i);
}
✗
int i;
for(i = 0; i < 5; i++) {
printf("%d\n", i);
}
✗
int i = 1;
for(; i < 5; i++) {
printf("%d\n", i);
}
Exception: Occassionally, you may need to use the counter in code below the for loop. In that case, declare and initialize outside the for loop (like Bad #1 above) and explain why in a comment.
✓
int divisor = 1; // will be used after the loop
for(; divisor < n; divisor++) {
…
}
return divisor;
Rule: Do not use global variables, except for constants, unless there is a clear and compelling reason.
✗
#include <stdio.h>
int g = 0;
int main(int argc, char* argv[]) {
g += 1;
…
}
✓
#include <stdio.h>
const int NUM_FEET = 2;
int main(int argc, char* argv[]) {
…
}
Note:
In ECE 26400, we will tell you if there is a clear and compelling reason. Otherwise, do not use global variables, except for constants (declared with const).
Exception: In rare occassions, there may be no meaningful value to initialize a variable to. Don't initialize to a meaningless value just to comply with this rule, since that could prevent Valgrind from warning you if you try to access an uninitialized value.
Rule: Use named initializers where possible and appropriate.
✓
struct Point point1 = {.x = 1, .y = 2};
✗
struct Point point1;
point1.x = 1;
point1.y = 2;
✗
struct Point point1 = {1, 2};
Reason: Struct initializers are clearer to read. They also prevent mistakes where programmers sometimes forget to initialize one field, or perhaps edit the code later and inadvertently delete part of the initialization. Bad #2 is actually fine in earlier versions of c (e.g. c89), but the new syntax shown as Good prevents errors where you get the order wrong.
Rule: Do not use 1- and 2-character names, except as allowed below if it is clear.
Allowed: i, j, k as counters for a 0-based loop.
Allowed: x, y, z as coordinates in an image or physical space.
Allowed: x1, x2, y1, y2, z1, z2 as bounds of a rectangle in an image or physical space.
Allowed: w, h, d as dimensions of an image or physical object/space; width, height, and depth, respectively.
Allowed: r, g, b, a as color components red, greed, blue, and alpha, respectively.
Allowed: n as the primary operand to a generic calculation (e.g., sqrt(…) in small scopes (≤10 lines).
Allowed: n as a number of something in small scopes (≤10 lines).
Allowed: ch or c as a char in a small scope (≤10 lines) where there are no other variables of type char
Allowed: s as a char* in a small scope (≤10 lines) where there are no other variables of type char*.
Allowed: any short, meaningless variable name in a demonstration or explanation of programming language features in which the content of the variable is irrelevant
Reason: The above exceptions are widely understood. Using very short names that are not understood forces the programmer/reader to search for the definition or else assume what they think it means.
Note:
Many in industry and open source, many would find these too loose (i.e., prefer less use of single-letter variables). For the class, we are erring on the permissive side to avoid being too draconian.
Rule: Loop index variables should be named “▒▒▒_idx” for 0-based loops, or “▒▒▒_num” for 1-based loops, where ▒▒▒ is a singular noun. In small scopes (≤10 lines), “i”, “j”, or “k” are also acceptable for 0-based loops.
Rule: Variables and struct members that represent a dimension, distance, other measurable quantity should be named with a singular noun or noun phrase their meaning and/or units (if applicable).
Rule: Variables and struct members that represent the number of items in an array, string, or list may be named according to the rules for quantities (above) −OR− “▒▒▒_size”, “▒▒▒_length”, or “▒▒▒_len” (where ▒▒▒ is the name of the array/string/list variable).
Rule: Variables and struct members of type char* that represent a string should be named with a singular noun that describes the contents or role of the string (e.g., person_name) or “▒▒▒_str”. In very small scopes (≤10 lines), s is allowed.
Rule: Variables and struct members that contain a memory address must begin with “a_” (or “a” if you are using lower-camelcase), with a few exceptions described below.
Exception: Do not use the “a_” prefix for strings, list nodes, and other objects primarily referred to by their address.
Note:
Outside of this class, you will see “p” or “ptr”. For pedagogical reasons, we use the word “address”.
Reason: This warns readers of your code (including you) that the meaning may be hard to understand. It also lets them know that it may change, so it's best not to refer to it from elsewhere.
Note:
"Internal" means things that should not be called from outside the current file or context. This rule is more important for larger projects, and is pointless for projects that have only a single file. However, real projects tend to grow, so it is a good habit to build on.
Rule: Variables and struct members representing a linked list node (e.g., Node) or address thereof (e.g., Node*) should be should be named “node”, “▒▒▒_node”, “next”, “prev”, “curr”, “head”, or “tail”. The head may also be called “▒▒▒_list”.
Rule: Variables and struct members representing/returning such a tree node (e.g., TreeNode) or address of a tree node (e.g., TreeNode*) should be should be named “node”, “▒▒▒_node”, “root”, “curr”, “head”, “right”, “left”, or “leaf”.
Rule: Do not use the casting operator in any context—including malloc(…)—unless absolutely necessary and safe for reasons that you completely understand well. Do not add a casting operator "just in case," or to silence warnings that you don't understand.
✗
char* s = (char*)malloc(10 * sizeof(*s));
✓
char* s = malloc(10 * sizeof(*s));
✓
unsigned int n_abs = (unsigned int)(n < 0 ? -n : n);
// ∙ Necessary because compiler prevents assigning signed to unsigned, in
// case it is negative.
// ∙ Safe because this can never assign negative value to the unsigned int
Spring 2019: OK to use snippet above for int ⇒ unsigned int
Rule: Body of a function definition or if/for/while/do/switch statement must be indented, relative to the lines that contain the enclosing curly braces.
Exception: In rare cases, you might want a loop or function with an empty body. In those cases, you may have both braces on the same line as the beginning of the loop. Also, note that this rule does not apply to struct or array initializers.
Rule: You can use either tabs or spaces to indent your code, but you must be consistent.
Option A:
tabs
Option B:
spaces
Note:
Tabs are recommended because the starter files will usually use tabs. If you prefer spaces, edit your .vimrc to change set noexpandtab to set expandtab. In addition, you will need to call :retab! on any starter file to convert tabs to spaces. Your vim modeline should include 'et' instead of 'noet'. You are welcome to choose a different tab size (i.e., instead of 4), but the aforementioned caveats apply there, as well.
Reason: If you mix tabs and spaces, your file layout may be hard to read for others with a different tabstop setting.
Rule: Comment code that is complex, or where the purpose of a block of code would be unclear at first glance. Do not include comments for things that are extremely obvious.
✓
// Calculate distance between corners of the rectangle
var distance_diag = sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1));
✗
/*
*
* MAIN FUNCTION
*
* This is the main program code. Execution starts here.
*
*/
Reason: This ensures that when course staff in ECE 264 view your code, they will have the same indent type that you had. It's also a good habit when working on projects where others might be using Vim (outside of this class).
Good: (for those who prefer tabs)
/* vim: set tabstop=4 shiftwidth=4 fileencoding=utf-8 noexpandtab: */
Good: (for those who prefer spaces)
/* vim: set tabstop=4 shiftwidth=4 fileencoding=utf-8 expandtab: */
Rule: Use printf(…) only for required output. For debugging, use #define to create a log macro that you can turn off with a flag to the compiler (i.e., conditional compilation).
Rule: Use assert(…) to catch bugs in your code. Do not use assert to catch anything that the user could control, external issues with the system, or anything else outside of your code.
Rule: Do not use 1 and 0 for true and false. Instead, use the true and false values and the bool type from the stdbool.h library. We are using the C99 version of the C language standard in this class, which includes stdbool.
Note:
We are using the C99 version of the C language standard in this class, which includes stdbool.
✗
int is_positive = (a > 0);
✓
bool is_positive = (a > 0);
✗
while(1) { … }
✓
while(true) { … }
Reason: The constants true and false make your code more readable because express what you mean. Since stdbool.h is part of the standard library, they will be readable by others and interoperable with other code.