TO THE ATTENTION OF CMPE 223 STUDENTS
GUIDELINES TO ACHIEVE GOOD PROGRAMMING
This document is intended to help you in writing well-developed
and neat programs. If you follow the guidelines below, it will
be very useful both for you (as the program coders and developers)
and for us (to grade your programs at best).
First of all, you should not forget that writing a program is
a self-discipline. You should bear in your minds that you are
not the only person who will deal with your programs. Moreover,
properly written programs would be very helpful to you, as well,
in debugging, and also in checking for the control flow.
Steps in a Programming
Project
In CMPE150, the importance of a clear, structured programming
style has been emphasized. Because of the importance of these
principles, it is worth reviewing them here before we start the
main part of CMPE223.
Any programming project, no matter how simple, should be divided
into several main sections:
- Make sure you understand the problem you are trying to solve.
This may seem obvious, but many later problems in coding can be
traced back to your lack of a complete understanding of just what
it is the program is supposed to do. In particular, when you are
doing CMPE223 lab questions, make sure you understand what the
question is asking. If you are at all unsure, ask someone, or
mail the instructor (akin@dec001.cmpe.boun.edu.tr) or TA (cemgil@dec002.cmpe.boun.edu.tr).
- Once you have a clear picture in your mind of the problem,
you should write out the specifications that the program is to
satisfy. As a simple example, suppose you wish to write a program
that sorts a list of numbers into ascending order. Even for such
a simple program, you need to consider the specifications before
you put finger to keyboard. For example, how are the numbers to
be input into the program? Will they be typed in from the keyboard
or read from a file? Will the quantity of numbers be known in
advance, or should this quantity be requested from the user before
the numbers are read, or should the program count the numbers
as they are read in? Where is the sorted list to be output (on
screen or in a file)? If files are used for input and/or output,
are the filenames to be constant, or should the program request
the filenames from the user? Should the program be able to sort
integers or real numbers as well? You should have made up your
mind on all these points before you even sit down in front of
the computer. Failing to think things through at this stage can
mean a lot of chopping and changing in the program later, which
leads to jumbled code that is full of errors, and difficult to
debug, as well as a lot of wasted time.
- Having decided exactly what the specifications of the program
are, you must now consider the coding of it. There are two aspects
to be solved here. First, you must decide what data structures
are to be used to represent the entities in the program. Second,
you must decide what algorithm you are going to use to do the
calculations.
Example: In coding a sorting program, for example, you
must decide how you are going to store the numbers to be sorted.
If the maximum quantity of numbers is known in advance, an array
may be the best way. If the quantity varies a lot from one run
to the next, a linked list (a data structure in which space is
allocated for data as it is needed, not reserved in advance as
in an array) may be more appropriate. If the numbers are associated
with other data, it might be better to use an array or linked
list of records, in which one field represents the number which
is used as the sorting key. If you have considered the specifications
properly, the choice of data structure is usually a lot easier.
The choice of which algorithm to use is often closely linked with
the specifications and the choice of data structures. There are
a great many sorting algorithms around (insertion sort, bubble
sort, quicksort, mergesort, etc. etc.). Different algorithms work
best in different situations-some sorting algorithms are more
efficient when the list to sort is short, others are better when
the list is long. Some algorithms move the data around less than
others, so if each chunk of data is fairly large, meaning that
moving it is expensive in computer time, it would be better to
use one of the former algorithms. If each datum is only a single
integer, so that moving it is fairly cheap, one of the other algorithms
may be better. If the program will be handling lists of different
sizes, it may even be worthwhile to include more than one sorting
algorithm, and choose the right one for the length of list currently
being sorted.
- The steps up to now could all be done away from the computer,
but you must now write the actual code. There are many guidelines
which are appropriate here, but we will consider these in a moment.
Overall, the goal is to write clear, well documented, easy to
read code that will be easy for both you and someone else to understand
and update at some time in the future.
- Once the program is written, it must be tested against its
specifications. Actually, it is a good idea to make testing an
integral part of the code writing process. Since properly written
code uses the top-down (stepwise refinement) process, you will
be writing individual functions and procedures to do separate
tasks. You should test each of these modules as you write it.
If a module calls other modules which you haven't written yet,
create a dummy or stub module with a simple writeln statement
in it so that your module has something to call. If you make sure
that each module works as you write it, you ease the task of debugging
things later on. Once the entire program is complete, though,
you must perform a set of comprehensive tests on it to make sure
it lives up to its specifications. Try it first with all forms
of acceptable input for which it was designed and make sure it
behaves correctly. However, one aspect (the most difficult one)
of testing is to try to anticipate all the forms of incorrect
input that users will feed your program. For example, suppose
your array sorting program is only designed to handle lists of
up to 100 numbers. What will it do if someone feeds in 200? Or,
if the program is designed to read a fixed quantity of numbers
from a file with a fixed name, what will the program do if the
file contains too few numbers, or if the file doesn't exist at
all? In most cases like this, the program will just crash with
an obscure error message, like 'Segmentation fault' or
'Trace-trap error', which will mean nothing to a naive
user. You should try to anticipate as many of these problems as
you can, and write your program to catch these errors and print
out friendly error messages that let the user know exactly what
is wrong. One of the best ways of doing this is have a friend
(preferably a sadistic one) consciously try to break your program
by feeding it all sorts of garbage input. You will find that there
are quite a few input errors that you haven't thought of. Although
this sort of testing is very important when you write programs
in a commercial or industrial environment, you won't have enough
expertise at present to be able to correct all the possible incorrect
input situations. (For example, how do you guard against someone
entering text characters when your program expects numbers?) To
trap all these cases requires some fairly sophisticated techniques
which you aren't expected to know at this stage. However, you
should be thinking about making your program as user-friendly
as possible by allowing for simple errors that you do know how
to fix. Unfortunately, putting in all these error traps can considerably
lengthen the code for an otherwise simple program. Most of the
programming examples we will consider in CMPE223 are designed
to illustrate the implementation of certain data structures in
Pascal, so we don't want them to get too complicated. In order
to emphasize the data structures, we will often not bother putting
in all the error checks that a fully developed program should
have. Although, the programs in these notes may not be of a standard
where they could be released into the world, they do provide some
good practice for you in attempting to find input that can cause
them to crash. When you read through the programming examples,
you should be on the lookout for things that could be done to
improve them. A check list that can be used for testing:
- Test the program with values that produce calculations that
are simple enough to check by hand.
- Test the program with realistic values (ones that the program
would expect to use in practice).
- Test the extreme cases (when you use all the space allocated
in an array, the highest or lowest acceptable number, etc.).
- Test what happens when inadequate or no input is present,
especially from a file.
- Test what the program does for `garbage input'. Try to produce
sensible error messages and thus avoid the GIGO (garbage in, garbage
out) syndrome.
- Make sure that every module in your program is actually used
in one of the test situations. For example, you may have written
a function that is only called if the input value of one variable
is 1.45. If that is so, try the program with an input of 1.45.
Programming Style
The actual style used in writing programs is often a matter of
personal taste, but there are certain guidelines that should be
followed within your own style in order to make programs easy
to maintain and understand by both yourself and others.
- For any program, no matter how simple, use a top-down approach
to coding. This means that you should split up the problem into
sub-problems, and split each sub-problem into sub-sub-problems,
and so on, until you have reduced the original problem to a collection
of tasks, each of which should consist of one specific calculation.
A practical guide to when this goal has been reached is that the
function or procedure for solving each task should not exceed
one screen full in length.
- Try to avoid unconditional branching in programs (as exemplified
by the goto statement). Contrary to popular belief, there actually
are a few places where it is appropriate to use goto, but these
are rare. The most likely place to find a goto is deep within
a set of nested loops, where it can be used to branch out if a
non-recoverable error should occur. For example, if a segment
of Pascal had the form:
while i < 1000 do begin
while j < 1000 do begin
while k < 1000 do begin
various statements;
if (disaster occurs) goto (escape)
various statements;
end;
end;
end;
- It is legitimate to have a goto here, since otherwise you
would probably have to include a test in each while statement
to guard against the disaster, which can be cumbersome to code.
Except for cases like this, however, it is best to avoid goto's.
Use for, while, repeat etc. instead-it is always possible
to do this!
- Avoid global variables. Using local variables and parameters
in each procedure makes them portable: if each procedure contained
variables used in other procedures, you couldn't transplant it
to other programs. For example, the binary search routine is useful
in many different settings, so a procedure that implements a binary
search should be written so that it can accept any data, by having
all its variables declared internally. One of the bad features
of Pascal is that any variables used in the main routine must
be global. Any parameters passed to and from functions and procedures
called from main will therefore need to be global.
- Use "obvious" names for your variables. If you are
calculating the average of a set of ages, for example, where the
ages are stored in an array, you should call the array elements
something like age[i] and the variable in which the average is
stored `average', rather than x[i] and y, say. In the same vein,
use constants to represent fixed numbers rather than just writing
in the number itself. For example, if you encountered the statement
c := 6.283185 * r;
- you may not immediately recognize what is happening, but if
you declared a constant
const
pi = 3.141592;
- and used more obvious names for your variables:
circum := 2*pi*radius;
- it is more obvious what's going on.
- Use comments intelligently. Every program and procedure should
begin with a commented description saying what the program does.
Each variable should have a comment on the line where it is declared
to say what it is used for. Major program sections should have
comments indicating what is happening at that point. Although
comments are useful, avoid overusing them. For example, the line
i := i+1; {increment i by 1}
- does not need the comment. Such comments are more distracting
than useful. Make sure your comments are meaningful. One comment
that occurred in a real systems program was:
{Horse string length into correctitude}
- This is, of course, unintelligible. If you use meaningful
names for your variables, detailed comments often become unnecessary.
- Make your program user-friendly. Always give clear prompts
when the user is expected to input data. Try to anticipate the
mistakes a user will make when entering data and provide helpful
error handling code to cope with them. For example, if the user
is supposed to input a number which must lie between two values,
say 0 and 100, the code should look something like this:
repeat
write('Enter n (0 <= n <= 100): ');
readln(n);
if (n < 0) or (n > 100) then
writeln('Number outside acceptable range. Please try again.');
until (n >= 0) and (n <= 100);
- Note that the code provides an error message and gives the
user another chance to get it right. This is much better than
just printing an error message and then stopping the program,
since the user has to rerun the program. This is especially true
if several values must be input, since the whole procedure would
have to be repeated.
Keep in mind that the programs you hand in during CMPE223 will
be marked partially on how well you have observed these criteria.
SUMMARY
- Project structure:
- Understand the project.
- Write out the specifications (specs).
- Choose data structures and algorithms.
- Write code
- Test code
- Programming style:
- Use top-down approach.
- Avoid goto's.
- Avoid global variables.
- Use meaningful variable names.
- Make your program user-friendly.
Every project you supply should come with a design document. The
specification of the design document is as follows:
THE DESIGN DOCUMENT
Outline
Design Environment
Implementation Details
Data Elements
File Dictionary
Module Dictionary
Design Environment:
The design environment is the physical environment where your
programs work. The hardware restrictions ( e.g., memory, disk,
mouse requirements), and the compiler types if necessary should
be explained.
Implementation Details:
Data Elements: The data elements may be the global variables
and/or the most frequently used variables of your programs.
Data elements of your programs should be well explained. A
graphical explanation is favorable.
File Dictionary: File dictionary is important when the
program uses more than one file. The file dictionary lists all
the files that the program accesses. The access methods and the
functions of these files should be clearly explained in the file
dictionary.
Module Dictionary: The module dictionary contains information
about the important modules (functions and procedures) of your
programs. The list should contain the followings:
- Module name: name of the module
- Functionality: the function that the module has to
perform
- Argument List: the arguments that are passed to the
module when calling
- Preconditions: the conditions, if any, that should
be satisfied before the module is invoked
- Postconditions: the conditions, if any, that should
be satisfied after the module is invoked
- Invocation Type: the style how the module is invoked
- List of modules invoked by the module
- List of modules which invoke the module