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 CMPE160.
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 CMPE160 lab questions, make sure
you understand what the question is asking. If you are at all unsure, ask
someone, or mail the instructor (akin@boun.edu.tr) or TA.
-
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 CMPE160 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 C++ had the form:
while i < 1000 {
while j < 1000 {
while k < 1000 {
various statements;
if (disaster occurs) goto (escape);
various statements;
}
}
}
-
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 double 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:
do
cout<<"Enter n (0 <= n <= 100): ";
cin>>n;
if ((n < 0) ||(n 100))
cout<<"Number outside acceptable range. Please try again."<<endl;
while (n > 100) || (n < 0);
-
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 CMPE160 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.