This is for Fall 2017 (7 years ago) only.
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.
The rules in this document are adapted from a variety of sources. While some might disagree on a few small points, most of these reflect prevailing views on C code style.
Summary
In case of any inconsistency, the descriptions below take precedent over this table.
Required
Global constants
Rule: Use all caps with words separated by underscores
Reason: This makes code more readable by making it clear which variables might change.
Good: const char* COURSE_NAME = "Advanced C Programming";
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.
Good: // Calculate distance between corners of the rectangle
var distance_diag = sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1));
Bad: /*
*
* MAIN FUNCTION
*
* This is the main program code. Execution starts here.
*
*/
Struct names
Rule: Use camel case with the first letter capitalized
Reason: This makes types look very different from instances of those types.
Good: struct ListNode{ … };
Internal helper functions or global constants
Rule: Begin with an underscore.
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.
Good: void _do_something_mysterious() {
}
Good: const int _MAX_BUFFER_SIZE = 128;
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.
Braces for if, for, while, switch, do, …
Rule: Always required. (Do not use inside each case of a switch, but use for the switch itself.)
Reason: Even though C will let you omit the braces, if the body is just one line, it
Good: if(a < 1) {
foo();
}
Note: Apple had a major security bug due to someone omitting the braces (
article).
indent body of function/if/for/while/do/switch
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.
Good: for(int i=0; i<5; i++) {
printf("i == %d\n", i);
}
Good: for(int i=0; i<5; i++)
{
printf("i == %d\n", i);
}
Good: (or at least acceptable)
if(a == 0) {
foo();
} else {
bar();
}
Bad: for(int i=0; i<5; i++) {
printf("i == %d\n", i);
}
Bad: for(int i=0; i<5; i++) {
{
printf("i == %d\n", i);
}
Bad: for(int i=0; i<5; i++) {
{
printf("i == %d\n", i);
}
Bad: for(int i=0; i<5; i++)
{ printf("i == %d\n", i); }
Bad: for(int i=0; i<5; i++) { printf("i == %d\n", i); }
Good: (ugly, but marginally acceptable, at least in ECE 264)
for(; s != '\0'; s++) { }
Multiple statements on one line
Rule: Not allowed
Reason: It makes code unnecessarily hard to read.
Good #1: int a = 1;
int b = 2;
Good #2: int a = 1;
char c = 'c';
Good #3: (ugly, but marginally acceptable, at least in ECE 264)
for(; *s != '\0'; s++) {}
Bad #1: int a = 1; char c = 'c';
Bad #2: for(int i=0; i < 5; i++) { printf("i == %d"); }
Dead code
Rule: Not allowed
Reason: This makes code harder to debug by concealing potential problems.
Bad: if(1 == 0) {
doSomething(); // will never get here!!!
}
Bad: int main() {
return EXIT_SUCCESS;
}
void _foo() { // not called by main(…) or anything else
// ...
}
Good: int main() {
_foo();
return EXIT_SUCCESS;
}
void _foo() {
// ...
}
Note: "Dead code" is code that is never called.
Useless variables
Rule: Not allowed
Reason: This makes code harder to debug by concealing potential problems.
Bad: int foo(int a) {
int b = 1; // never used!!!
return a * 10;
}
Useless assignments
Rule: Not allowed
Reason: This makes code harder to debug by concealing potential problems.
Bad:
int foo() {
int a = 0;
a = a + 10; // no effect!!!
a = 20;
return a * 10;
}
Note: Useless assignments are assignments that cannot, under any circumstances, have any effect on the output.
Exception: This does not apply variables that are initialized to 0 or NULL (or similar).
Vim modeline:
Rule: Required
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: */
Constants
Rule: Declare with const, not #define
Reason: const enforces types. #define just does a simple text substitution.
Good: const int NUM_ASSIGNMENTS = 15;
Bad: #define NUM_ASSIGNMENTS 15
Exception: It's okay to use #define for constants if you have a very specific reason. For ECE 264, you must document the reason in a comment.
Variable names
Rule: Must describe contents
Reason: This makes code more readable, especially when you come back to it later or show it to someone who didn't read it (e.g., when asking for help).
Exception: For array indices or other variables that are used only in a small portion of the code where the meaning is completely obvious at first glance, it is okay to use one or two letters (e.g., i for an index, s or a string, ch for a string, n for some other number). Also, short names are okay for small examples to illustrate programming concepts where brevity is a goal in itself.
NULL vs. 0 vs. '\0'
Rule: Use NULL for addresses (i.e., null pointers), 0 for numeric values, and '\0' for characters (e.g., string terminator).
Reason: This makes code more readable and avoids bugs.
Note: Internally, NULL, 0, and '\0' are all stored as 0.
Declarations
Rule: Declarations initialize
Good: int i = 0;
Bad:
int i;
i = 0;
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.
for loop counter scope
Rule: Declare and initialize for loop counter in the for statement.
Good: for(int i = 0; i < 5; i++) {
printf("%d\n", i);
}
Bad #1: int i = 1;
for(; i < 5; i++) {
printf("%d\n", i);
}
Bad #2: int i;
for(i = 0; i < 5; i++) {
printf("%d\n", i);
}
Exception: In rare occassions, 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.
Boolean values
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.
Good: bool is_positive = (a > 0);
Bad: int is_positive = (a > 0);
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.
Array initializers
Rule: Use where possible (and appropriate)
Good: int numbers[] = {1, 2, 3};
Bad: int numbers[3];
numbers[0] = 1;
numbers[1] = 2;
numbers[2] = 3;
Reason: Array initializers are clearer to read, and avoid certain types of bugs (e.g., numbers[3] = 4 for an array of size 3.).
Struct initializers
Rule: Use where possible (and appropriate)
Good: struct Point point1 = {.x = 1, .y = 2};
Bad #1: struct Point point1;
point1.x = 1;
point1.y = 2;
Bad #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.
Data type sizes
Rule: Do not make assumptions about the size of any data type. Use the sizeof(…) operator instead.
Good: char* s = malloc(n * sizeof(*s));
Bad: int* array = malloc(n * 4);
sizeof argument
Rule: Argument must be a variable name when used in an initialization or malloc(..)
Good: struct Node* new_node = malloc(sizeof(*new_node));
Bad: struct Node* new_node = malloc(sizeof(struct Node));
Good: int* array = malloc(n * sizeof(*array));
Bad: int* array = malloc(n * sizeof(int));
Reason: This reduces duplication of information. If you change the type of the variable later, this will automatically adjust.
printf
Rule: Use printf only for "official" 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).
Good: #ifdef DBG_LOG
#define dbg_log(...) printf(__VA_ARGS__)
#else
#define dbg_log(...)
#endif
int main() {
dbg_log("Starting...\n");
}
Bad: int main() {
printf("Starting...\n");
}
assert
Rule: Use assert only 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.
Global variables
Rule: Do not use global variables, except for constants, unless explicitly allowed for an assignment.
Bad: #include <stdio.h>
int g = 0;
int main(int argc, char* argv[]) {
g += 1;
…
}
Good: #include <stdio.h>
const int NUM_FEET = 2;
int main(int argc, char* argv[]) {
…
}
Character literals
Rule: Use the character literal (e.g., 'A'
) and not the integer literal (e.g., 65
) if the data represents a character.
Bad: char digit = 48 + n % 10;
Good: char digit = '0' + n % 10;
Casting operator
Rule: Do not use the casting operator with malloc(…)
or in any other context, unless it is absolutely necessary for a reason that you completely understand well. Do not add a casting operator "just in case," or to silence warnings that you don't understand.
Bad: char* s = (char*)malloc(10 * sizeof(*s));
Good: char* s = malloc(10 * sizeof(*s));
Your choice - pick one and be consistent within a given project
Multi-word variable and function names
Option A: int list_idx = 0;
Option B: lowercase camel case
int listIdx = 0;
Braces for if blocks
Braces for functions
Indent type
Option A: tabs
Option B: spaces
Note: This must be reflected in your vim modeline (see above). Also, you are welcome to choose a different tab size (i.e., instead of 4).
Reason: If you mix tabs and spaces, your file layout may be hard to read for others with a different tabstop setting.
Spacing
Option A: one space between functions
Option B: two spaces between functions