Programming Style Guide
It has been said that `programs must be written for
people to read, and only incidentally for machines to
execute' (ref. H. Abelson and G. J. Sussman,
Structure and Interpretation of Computer
Programs, MIT Press, preface to the first
edition). Therefore, as discussed in class, it is
important to follow some basic guidelines for writing
source code. Follow the guidelines below for all
programming assignments. Note: we may evolve this
set of guidelines as the course progresses.
Remember, assignments provide you with an opportunity
to show us that you care enough to submit a
professionally-prepared submission. Practice good
programming habits early and you will be rewarded
with effective and efficient programs. Following this
guide will improve the readability, writeabiliy, and
maintainability of your programs and therefore reduce
the likelihood of costly errors which will save you
time in debugging. A portion of your grade for all
work will be evaluated for style.
- Source code files must be readable by vi and
contain only UNIX newlines (only line feeds). In
other words, source code files must not contain
non-UNIX newlines (line feed and carriage return
pairs, e.g., ^M s).
- Assignments must be prepared exclusively using
UNIX systems.
- Begin each source file with the following
header filled-in appropriately.
/*******************************************************************************
/
/ filename: env.c
/
/ description: Implements the UNIX env utility.
/
/ author: Last, First
/ login id: cps444-n1.xx
/
/ class: CPS 444
/ instructor: Perugini
/ assignment: Homework #1
/
/ assigned: January 18, 2006
/ due: January 25, 2006
/
/******************************************************************************/
- Begin each shell script file with the following
header filled-in appropriately.
#*******************************************************************************
#
# filename: filter
#
# description: Implements a filter script.
#
# author: Last, First
# login id: cps444-n1.xx
#
# class: CPS 444
# instructor: Perugini
# assignment: Homework #1
#
# assigned: January 18, 2006
# due: January 25, 2006
#
#*******************************************************************************
- Do not allow any line of code to exceed
80 characters in length. Most text editors
have an option to give you column position. Find an
appropriate place to break long program statements
to continue them on the following line. Break long
character strings using string concatenation.
- Indent all code within a block.
- Do not use tabs anywhere in your code. For each
level of indentation, use three spaces. Tabs cause
different amounts of horizontal spacing on
different systems. By using spaces (and a
fixed-width font), you guarantee your code will be
properly indented for every system, editor, and
printout.
- Align corresponding opening and closing braces,
begin or ends, or any other
program unit delimiters. My preference for curly
braces { } (or similar delimiters) is to
always place the opening brace on the same line as
the block it opens. This makes it easy to see where
blocks of code, such as loops, begin and end, and
does not waste a line of code. An alternate style
is to place each brace on line by itself. You may
use either of these styles, but do not mix them.
Always be consistent.
- Use descriptive (variable, constant, procedure,
function) identifiers and use appropriate naming
conventions for variables (total_sold) and
constants (OUNCES_PER_TON). Remember,
syntax should imply semantics.
- Cryptic: int x, y, z
- Descriptive: int dollars,
average, weight
- Initialize variables (to a value of the
appropriate type) before you use them to avoid
garbage. This can be done when you declare the
variable or with an assignment statement before the
variable is used.
- Incorrect: double radius =
3;
- Correct: double radius =
3.0;
- Avoid type mismatches. Following this guideline
will make your programs more portable.
- Consider:
-
char c;
while ((c = getchar()) != EOF) {
...
}
- Do not assign a variable or literal of one type
to a variable of another, even if our
compilers/interpreters permit it. Following this
guideline will make your programs more portable.
- Consider: double avg_score =
76.7; int exam1 = 86;
- Incorrect: avg_score =
exam1;
- Correct: avg_score =
static_cast (exam1);
- Consider: double average =
0.0; int total = 967, num_students = 10;
- Incorrect: average =
total/num_students;
- Correct: average = static_cast
(total)/num_students;
- Do not use goto.
- Avoid the use of global variables.
- Avoid the use of static variables.
- Use comments to explain critical subsections or
any ambiguous parts of your programs (e.g., a
tricky expression).
When writing C code, only use C /* ... */
style comments (even if your compiler supports C++
// ... style comments); this will make
your programs more portable.
If supported, do not use multi-line comments where
they are likely to make your program less readable.
- Use named constants rather than magic numbers.
This gives you a single point of modification which
will save you time and reduce bugs.
examples:
#define SIZE 76
const int NUMBER_OF_RECORDS = 101;
const char A = 'a';
const char E = 'e';
const char I = 'i';
const char O = 'o';
const char U = 'u';
switch (character) {
case A:
case E:
case I:
case O:
case U:
}
- Always use enumerated types where they
make your code more readable.
example:
typedef enum { JAN = 1, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC } months;
main() {
months my_months;
switch (my_months) {
case JAN:
...
break;
case FEB:
...
break;
...
case NOV:
...
break;
case DEC:
...
break;
}
}
- Enforce the principle of least privilege.
- Avoid using local variables with same name in
different scopes (they are different variables).
- Always exit from main with a
0 exit status to indicate success and a
non-zero status to indicate failure. Use
exit rather than return to make
your program more uniform. Use an int as a
return type for main.
example:
int main() {
FILE* fp = NULL;
char* filename = "input.txt";
if ((fp = fopen (filename, "r")) == NULL) {
fprintf (stderr, "cannot open %s\n", filename);
exit (1);
} else {
...
exit (0);
}
- Always initialize pointer variables.
examples:
Node* node_ptr = NULL;
char* filename = "input.txt";
FILE* myinstream = fopen (filename, "r");
- Avoid allocating more memory than necessary for
anything.
- When allocating memory, always verify that the
memory was allocated successfully.
example:
if ((node_ptr = malloc (sizeof (Node))) == NULL) {
fprintf (stderr, "out of memory!");
exit (1);
} else {
...
exit (0);
}
- Once finished, always free memory that you
explicitly allocated.
example:
if ((node_ptr = malloc (sizeof (Node))) == NULL) {
fprintf (stderr, "out of memory!");
exit (1);
} else {
...
free (node_ptr);
exit (0);
}
- When opening a file, always verify that the
file was opened successfully.
example:
if ((fp = fopen (filename, "r")) == NULL) {
fprintf (stderr, "cannot open %s\n", filename);
exit (1);
} else {
...
exit (0);
}
- Always close files that you explicitly opened.
example:
if ((fp = fopen (filename, "r")) == NULL) {
fprintf (stderr, "cannot open %s\n", filename);
exit (1);
} else {
...
fclose (fp);
exit (0);
}
- Always print error and debugging messages to
stderr (output written to stdout
is line buffered).
example:
if ((fp = fopen (filename, "r")) == NULL) {
fprintf (stderr, "cannot open %s\n", filename);
exit (1);
} else {
...
}
- Avoid buffer overflows.
- Consider:
-
char password[17];
printf ("Please enter your password: ");
- Incorrect: scanf ("%s",
password);
- Correct: scanf ("16%s",
password);
- Use perror (errno.h) and/or
strerror (string.h) to display
error messages where appropriate.
- Make appropriate use of qualifiers such as
const, restrict,
volatile, and register on
function parameters and elsewhere (in the case of
const, volatile, and
register).
-
Functions:
- Always use a procedure/function
prototype.
- Use parameter names in procedure/function
prototypes.
- Use different identifiers for formal
parameters and actual parameters to reinforce
that they are different variables.
- Precede every procedure/function with the
following header explaining its purpose, the
meaning of each parameter, precondition,
postcondition, and the general strategy of its
implementation, if applicable.
/*******************************************************************************
/
/ purpose: To compute the factorial of a non-negative integer.
/
/******************************************************************************/
int factorial (int n) {
if (n == 0) then
return 1;
else
return n*factorial (n-1);
}
- No routine/subprogram, block, procedure,
function, or method (or message) should exceed
50 lines of code.
- The following guidelines are from UNIX
Systems Programming: Concurrency, Communication,
and Threads by K.A. Robbins and S. Robbins.
Prentice Hall, 2003 (pp. 29-30):
``Error handling is a key issue in writing
reliable systems programs. When you are writing a
function, think in terms of that function being
called millions of times by the same application.
How do you want the function to behave? In
general, functions should never exit on their
own, but rather should always indicate an error
to the calling program. This strategy gives the
caller an opportunity to recover or shut down
gracefuly.
Functions should also not make unexpected
changes to the process state that persist beyond
the return from the function. For example, if a
function blocks signals, it should restore the
signal mask to its previous value before
returning.
Finally, the function should release all the
hidden resources that it uses during its
execution. Suppose a function allocates a
temporary buffer by calling malloc and
does not free it before returning. One call to
this function may not cause a problem, but
hundreds or thousands of successive calls may
cause the process memory usage to exceed its
limits. Usually, a function that allocates memory
should either free the memory or make a pointer
available to the calling program. Otherwise, a
long-running program may have a memory
leak; that is, memory "leaks" out of the
system and is not available until the process
terminates.
You should also be aware that the failure of a
library function usually does not cause your
program to stop executing. Instead, the program
continues, possibly using inconsistent or invalid
data. You must examine the return value of
every library function that can return an error
that affects the running of your program, even if
you think the chance of such an error occurring
is remote.
Your own functions should also engage in
careful error handling and communication.
Standard approaches to handling errors in UNIX
programs include the following.
- Print out an error message and exit the
program (only in main).
- Return -1 or NULL and set an error
indicator such as errno.
- Return an error code.
In general, functions should never exit on
their own but should always report an error to
the calling program. Error messages within a
function may be useful during the debugging phase
but generaly should not appear in the final
version. A good way to handle debugging is to
enclose debugging print statements in a
conditional compilation block so you can
reactivate them if necessary.''
- The following guidelines are from UNIX
Systems Programming: Concurrency, Communication,
and Threads by K.A. Robbins and S. Robbins.
Prentice Hall, 2003 (pp. 30-31):
``Most library functions provide good models for
implementing functions. Here are some guidelines
to follow.
- Make use of return values to communicate
information and to make error trapping easy for
the calling program.
- Do not exit from functions. Instead, return
an error value to allow the calling program
flexibility in handling the error [Explicitly
set errno for all errors, and do not
rely on the fact that a function which fails
may set errno automatically for you.
Common errors include exceeding available
memory or file I/O open/close, read/write
errors. See the
GNU webpage for libc for a list error codes
which are #defined in error.h
(e.g., use ENOMEM for the former and
EIO for the latter errors
above)].
- Make functions general but usable.
(Sometimes there are conflicting goals.)
- Do not make unnecessary assumptions about
sizes of buffers. (This is often hard to
implement.)
- When it is necessary to use limits, use
standard system-defined limits, [e.g.,
MAX_CANON, #defined in
limits.h] rather than arbitrary
constants.
- Do not reinvent the wheel -- use standard
library functions when possible.
- Do not modify input parameter values unless
it makes sense to do so.
- Do not use static variable or dynamic
memory allocation if automatic allocation will
do just as well.
- Analyze all the calls to the
malloc family to make sure the program
frees the memory that was allocated.
- Consider whether a function is ever called
recursively or from a signal handler or from a
thread. Functions with variables of static
storage class may not behave in the desired
way. (The error number can cause a big problem
here.)
- Analyze the consequences of interruptions
by signals.
- Carefully consider how the entire program
terminates.''
-
Shell scripts:
- Always terminate with a proper
exit statement (0 for success and
non-zero for failure).
- Always start with a proper interpreter
directive.
#!/bin/sh
#!/bin/ksh
#!/bin/csh
- Be consistent in your application of the above
guidelines.
- Overall, write your programs such that they are
self-documenting. In other words, structure
your code such that the program itself provides its
own documentation. Self-documentation means using
descriptive identifiers and a consistent, aligned
format.
|