CSE 332 Programming Guidelines
These guidelines are intended to help you learn and practice good
software development habits, which in the long term can help you write
more reliable and readable programs and function more effectively as
part of a software development team. In the short term, developing
these habits also can help you avoid some of the more common mistakes that
have resulted in grading deductions in previous semesters.
A. Program Compilation, Execution, and Submission
Problems with program compilation and execution will often result in
major (5-10 point) deductions during grading, though for less severe
concerns (for example a compiler warning that should have been fixed
but does not indicate a deeper problem with the program, or forgetting
to add a usage message for a program) minor (1-3 point)
deductions are possible.
- Read each lab assignment carefully and completely, and make sure you
have done everything the assignment asks you to do. A common reason
for deductions on a programming assignment is failure to complete one
or more tasks requested for the assignment.
- Make sure you submit (all and only) the files needed to evaluate your
solution. Deductions may be made for including extraneous
automatically-generated files or for omitting necessary files from
the submitted code, and the deductions will increase in cost with
the number of occasions on which submissions include superfluous files or are missing files.
A good way to make sure all the files are submitted is to check
what you are about to turn in: (1) if you are working on Windows, are all the files you added,
the main file, and the ReadMe.txt file (but not any of the automatically
generated files like stdafx.h) present in the zip file you are going to submit? (2) if you are
working on Linux, are they all listed in the Makefile, did
make test_turnin, and if so did all the files
end up in the
TEST_TURNIN directory when you did that?
- Always submit a "readme" (or on Windows "ReadMe.txt") file that
documents what the submitted
program does, any places where your solution diverged from what
was requested in the assignment, and the design decisions you made
in developing your program. Please ask any remaining questions you may
have, which were not answered on the newsgroup or in lab, in your
readme file, and we welcome feedback on your impressions of the lab
(what was easy, what was difficult, suggestions, etc.).
- Test your program thoroughly. Don't just run it a few times
and assume it will work correctly when we grade it (using an
arbitrary set of tests we design). Instead, think about all the
ways your program might fail, and then design additional tests to
explore those cases. Think about all the different kinds of
inputs a user might provide: could certain inputs (or failure by
the user to provide those inputs) result in a divide-by-zero
error, an out-of-range index to an array, a stray pointer, or
other situations that are likely to lead to a program crash?
Similarly, will all allocated memory be released under all possible
error conditions, as well as along the "happy path" of program
- Always compile with the most stringent warning level that
won't complain about the libraries provided by the development
environment (e.g., with g++ on Linux use the
-Wall switch) to see all
possible compiler warnings about your code, and then fix all those compiler warnings before
submitting your program for grading. If a warning cannot be
fixed, and does not indicate a problem in your code, you may
use a compiler pragma to silence that specific warning, but
then please document that you have done so, and why it was
necessary, both in your readme file and in your code.
- Your program's
main function must return 0 on
successful termination, and a non-zero value otherwise. If a
number of different return values are provided, it's good to
document what the different return values mean both in the program
source code itself and in the usage message the program generates.
- Don't let a thrown exception propagate out of the main function
uncaught. Instead, always catch any exceptions that propagate
up to the main function and then return an appropriate non-zero
code to indicate program failure.
- Only use exceptions to unwind the program call stack under
abnormal conditions: do not use exceptions as a way to control
program execution under normal operating conditions.
- Avoid using the same error message for different failures
(this often results from "cut and paste" coding). Make sure
to document in your readme file all the error messages your
program can generate, and the conditions under which each one
- After calling a function that can fail (for example, opening a
file), always check for success or failure and then take appropriate
action if the function failed. On a few rare occasions there
really may be nothing useful to do if a function fails, in which
case it is only acceptable not to check the return value if you add
a comment explaining why there is nothing useful to do if the return
value indicated failure instead of success.
- Avoid unnecessary format restrictions, e.g., in general your
programs should tolerate blank lines and extra whitespace in
input files unless there is an overriding reason not to do
- Don't dereference a pointer whose value is zero or that points
outside the memory locations allocated to your program. If you
do so your program will end. Abruptly. This is called an
- Be very careful with pointer arithmetic, and ensure that any
non-zero pointer points to some object: if it is not a handle
to an object, then it is probably a "handle to an access
violation" if it's dereferenced (if you're lucky: see next
- Watch out for the "worst case scenario" with pointer
arithmetic mistakes. If a pointer ends up pointing to memory
your program owns, but not where you intended it to point, it
is possible to corrupt other parts of your program arbitrarily
simply by writing to the memory location where the "stray
pointer" points. Worms and viruses exploit this weakness of
C, C++, and other similarly "unsafe" languages by trying to
cause programs to overwrite their program call stacks or other
crucial areas, in order to give the worm or virus control over
the computer. If your program seems to be corrupted (this
often can be seen very well in a debugger), it's definitely
time to go back and check your pointer math for statements
that have executed up to the point when things got corrupted.
- Don't use fixed length buffers when the lengths of inputs
to them are highly variable or unknown. Instead, use the
string class for variable length character strings, and use
the vector class for variable length buffers.
- Debugging programs is part science and part art. The science
lies in (1) the methodical characterization of
program behavior; (2) the development of
hypotheses about why certain behaviors are or
are not happening; (3) making predictions based on
those hypotheses; and (4) designing and conducting (sometimes simple)
experiments to evaluate those predictions and
the underlying hypotheses. The art lies in creating good
hypotheses, predictions, and experiments that lead you quickly
and reliably toward an answer, and in thinking creatively about
what could happen. For more on this process, see the
Scientific method page.
One very helpful question to ask throughout the debugging
process is "what was the last point where my program seemed to
be working correctly, and what was the first point where it
seemed to be working incorrectly?" A debugger is in invaluable
tool in partially automating this process.
- Every program should have a "usage" message. It should be
printed out if erroneous command line arguments, or a
--help command line argument, are
provided to the program. In addition to showing the correct
format for command line arguments, the usage message should
briefly explain what the program does.
B. Program Design and Coding Idioms
Problems with use of coding idioms can result in either major
deductions (for example, causing a memory leak by failing to call
delete on memory allocated by new -- even better would be to use safe
memory management idioms to avoid this problem) or minor deductions (for
example, unnecessarily zeroing out a pointer before calling
delete on it) during grading.
- Whenever possible (which means always, in this course) use
delete instead of
free to allocate and deallocate
Whenever you use
new to allocate
memory in a program, make sure to find and document where in the
program that memory will be freed with a corresponding
delete. The best way to do this
is to use the safe memory management idioms we'll talk about
as the course evolves. A more general way to state this point
is that you should always know (and comments in your code
should explain where appropriate) the lifetime of any dynamically
allocated piece of memory.
free to deallocate memory
that was allocated with
Similarly, never use
deallocate memory that was allocated with
- Don't check for a pointer being 0 before deleting it. It's
always safe to delete a 0 pointer. If the pointer is visible
outside the local scope, it's often a good idea to set it to 0
after deleting it.
- Never compare or assign a pointer value with
NULL; use 0 instead. The
language allows any pointer to be compared or assigned with
0. The definition of NULL is
platform dependent, so it is difficult to use it portably.
- Avoid using dynamic allocation when using a local variable
(also known as an automatic variable) or class member variable
instead would do. If something has a lifetime that is contained
within the scope of a single function then dynamic allocation
should only be used when absolutely necessary, such as when the
size is determined dynamically at run-time according to the
parameter value(s) passed to that function. Similarly, if
something is dynamically allocated in the constructor and
dynamically deallocated in the destructor, then there must be some
reasonable design force that motivates this approach rather than
using an object directly as a member of the class (which your
readme file should of course please explain).
- Don't use exit() to end the program in the middle of a class
method, or in or any method that could be called from another
method. This interferes with the normal graceful shutdown of the
program, and the release of resources the program acquired during
its execution. It's better to use exceptions or return values to
cause the program to unwind its call stack and exit from its
- If a class has any virtual functions, and its destructor is
declared explicitly in the class, then the destructor should
always be virtual as well.
- Avoid default arguments unless there's a very good reason to
- Items in the constructor's base/member initialization list
should appear in the same order as the data members are declared
in the class header, and base class initializations should appear
before data member initializations. This helps avoid subtle
errors, because initialization takes place with base class
constructors being called first, and then data member constructors
in the order of member declaration.
- Don't initialize class members in the constructor body when it
is possible to intialize them in the base/member initialization
- Don't use functions that could fail in a constructor's
base/member initialization list: instead use a "safe" constructor
for each base class or member (possibly a do-nothing default
constructor) and then call the function in question in the
constructor body, where the function's return value can be checked,
exceptions can be caught, etc.
- Initialization is usually cleaner than assignment, especially in
a conditional. So, instead of writing code like this:
if ((n_bytes = foo.send ((char *) &reply_port,
sizeof reply_port)) == -1) // ...
Write it like this:
ssize_t n_bytes =
foo.send ((char *) &reply_port, sizeof reply_port)
if (n_bytes == -1) // ...
- Be aware when initialization of a static variable occurs. A
static variable is only initialized the first time its initialization
definition is seen, according to the order in which compilation units
(source files) are compiled.
- It is usually clearer to write conditionals that have both
branches using a positive (non-negated) condition. For example,
// true branch
// false branch
is preferred over:
if (! test)
// false test branch
// true test branch
- (Thanks to Prof. Ken Goldman for this one) Avoid unnecessary
use of conditional logic. For example simply say
return condition ? true : false;
- If a type cast is necessary, avoid use of C-style casts, e.g.,
(int) foo. Use standard C++ casts, e.g.,
static_cast<int> (foo), instead.
- If instances of a class should not be copied, then
a copy constructor and assignment operator should be
declared as private to the class, but then not defined.
- If instances of a class should not be created on the program
call stack, but only through dynamic memory allocation, then
declare and define a private (or possible protected) destructor.
If it's necessary to be able to create class instances from
outside the class (or its derived classes), then provide a
(possibly static) member function that creates instances of the
new. If it's necessary to be able to
destroy class instances from outside the class (or its derived
classes), then provide a member function that destroys instances
of the class using
- Never use
BOOL, or similar types, even if a
compiler supports them. Use the standard C++
type for Boolean variables, instead.
- If a loop index is used after the body of the loop, it
must be declared before the loop. For example,
size_t i = 0;
for (size_t j = 0; file_name [j] != '\0'; ++i, ++j)
if (file_name [j] == '\\' && file_name [j + 1] == '\\')
file_name [i] = file_name [j];
// Terminate this C-style string.
file_name [i] = '\0';
- Prefix operators are generally more efficient than postfix
operators. Therefore, they are preferred over their postfix
counterparts where the expression value is not used.
- Except for very simple expressions, use
if (...) else
.... instead of the contional
operator. Your code will be a lot more readable, and this
can help you avoid subtle bugs due to the precedence of
?: compared with other operators
in an expression.
- When a class provides
operator==, it must also provide
operator!=. Also, both these operators must be
const and return
- To avoid surprises when other code or debugging statements are
added, it's a good idea to put braces around the body of a
else statement branch, even if it's only a single
statement. For example:
for (int j = 0; j < argc; ++j)
cout << argv [j] << endl;
int k = 0;
cout << argv [k++] << endl;
} while (k < argc);
while (k > 0)
cout << argv [--k] << endl;
if (0 <= k && k < argc)
cout << argv [k] << endl;
cout << "argument position " << k << "is out of range." << endl;
- To break circular inclusion dependences, in addition to using inclusion guards,
selectively replace inclusions of other header files with forward declarations
of the items that would have been included,
and remember that in many cases templates can be forward
- check the return value from any call that could return an
error code after a failure (e.g.,
- use try/catch blocks around any call that could throw an
exception after a failure (e.g.,
- Narrow interfaces are better than wide interfaces. If there
isn't a need for a function or class member function, leave it
out. This eases maintenance, minimizes footprint, and reduces the
likelihood of interference when other functions need to be added
- Distinguish definitions from declarations by placing
definitions in source (
.cpp) files, placing
declarations in header (
.h) files, and using the
#include directive to make declarations visible where
needed. Note that some compilers use a different source file
.cxx) but for
this course we will always use the
.cpp extension for
our source files.
- Distinguish declarations for class templates and function
templates from declarations for non-template classes and functions
by placing them into separate header (
.h) files. Also
distinguish definitions for class templates and function templates
from definitions for non-template classes and functions by placing
them into separate source (
.cpp) files. This will
give you better decomposition of your programs overall, and some
compilers get confused when template and non-template code is
mixed in the same file.
- Protect header files against multiple inclusion with this "inclusion guard" construct:
// Class and function declarations go here...
#endif /* FOO_H */
Files that contain type-parameterized classes should follow this style:
// Class template and function template declarations go here...
#if defined (TEMPLATES_REQUIRE_SOURCE)
#endif /* TEMPLATES_REQUIRE_SOURCE */
#endif /* FOO_T_H */
Notice that some compilers need to see the code of the
template, in which case the
.cpp file must be
included from the header file. In that case to avoid multiple
inclusions of the template
.cpp file it should also
be protected as in:
// Class template and function template definitions go here...
#endif /* FOO_T_H */
- In a
.cpp source code file always include the
corresponding header file as the first inclusion (after any environment-specific
mandatory inclusions like
stdafx.h on Windows with precompiled headers), like this:
// This is Foo.cpp
#include "stdafx.h" // would not have this on Linux
// Any other includes go here...
// Definitions go here...
In this way we are sure that the header file is self-contained
and can be safely included from some place else (if it seems like other
includes should go first, then they probably belong in the corresponding
- If you don't use a declared argument to a function in the
function's body, give only the argument's type but not its
name in the definition signature, e.g.,
int main (int, char *)
// Rest of the definition of main ...
- Use smart pointers (such as
possible, in preference to using
C. Program Style and Documentation
Problems with program style and documentation usually result in
only minor deductions during grading, but even minor deductions
can add up quickly if there are a lot of them. In the longer
term, practicing a consistent and high quality programming style
will make your code more accessible to others, which is essential
in team programming environments.
- Always put a comment at the start of each source or header
file, with (1) the name of the file, (2) the names and e-mail
addresses of the authors of the code in that file (for this course
it will only be you), and (3) a brief summary of what the file
contains (for example, "(Lab 0) definitions for the namehash
program, which computes a simple hash value based on program
- Use comments and whitespace generously. Comments should
summarize what each non-trivial section of code does, and
indicate why you designed it the way you did.
- Avoid leaving "commented out" code fragments in submitted
code - remove these or use conditional compilation to compile
them in/out (by default they should be compiled out).
- Avoid commenting things that are already fairly obvious from
the code (e.g., begin and end of a method).
- Always give a pointer (or size) arithmetic formula a comment
explaining it: in general any non-obvious code should have a comment
giving an intuition of what it does in general terms (and even
better explaining why or how it does it that way).
- Avoid unnecessarily cramping symbols or lines together,
reducing readability (use lots of whitespace: it's free!)
while statements should have a
space after the respective control keyword, as in:
for (unsigned int i = 0; i < count; ++i)
- Avoid using hard coded constants like
use an appropriately named constant variable (or if absolutely
necessary a precompiler constant) like
static const int days_in_a_week = 7;
- Avoid including widely used character sequences (e.g., "
Error") in a source
or header file name, to make your code files easier to work with on different
platforms and in different development environments. For example,
GNU Make's error messages start with
Error", and so it's much easier to search for errors if
filenames don't contain "