A computer program is the result of many design decisions. These decisions
- why this variable was introduced, what that function does, etc. - are
often not reflected in the final Java code, which consists of low-level,
detailed declarations and statements. However, the higher-level design
must be understood if a programmer is to modify the program successfully.
Trying to understand decisions that are not recorded in comments in the
code is tedious, error-prone, and aggravating - but all too common.
So, it will be to your advantage to instill in yourself some disciplined
programming habits, right from the beginning, like:
-
Write a precise definition (in a comment) of a variable as you write its
declaration
-
Update the definition of the variable (the comment) when you decide to
change its meaning
-
Write a precise specification for a method (which says what it does) as
you write the method's heading - don't wait until after the method is completely
written or until the program is debugged.
-
Change the specification for a method whenever you make changes to the
body of the method that make the body not fit the specification.
Preparing all the comments after the program is finished is bad for three
reasons:
-
The comments were not of use to you when developing and debugging the program,
so you took more time than was necessary.
-
The comments are harder to write after the program is finished, because
it is difficult to remember the meaning of all the variables and methods.
-
It is quite likely that you won't write the comments - even programmers
with the best intentions don't spend much time filling in comments after
the fact, because there are too many other interesting things to do.
You will find that writing good comments as you write a program will help
you clarify your ideas and write better, correct code sooner. If you can
write down clearly what your program is doing you are more likely to have
a good understanding of the problem and your program is more likely to
be correct. Time spent on careful thinking and writing is more than repaid
in time saved during testing and debugging.
The Elements of Style contains several rules that are useful
for programming as well as writing. Among them are:
-
Omit needless words.
-
Use the active voice.
Follow these rules when writing specifications. For example, don't write
the specification "This function searches list x for a value y and ..."
or "Function isIn searches list x for a value y ..."'. Such specifications
are too wordy and are not commands but descriptions. Instead, say the following.
// Yield "y is in list x" boolean isIn(int y, List x)
Similarly, don't belabor the obvious. Comments shouldn't repeat the
code, such as "increment j" does for j++. Neither should comments contradict
the code. So when you change code, change the comment!
Use comments to clarify code, not confuse it. If you find a comment
growing longer than the code itself, it is probably the case that either
the comment is too long or the code is bad and needs rewritten. The best
way to rewrite such code is to make is self-documenting. This can be done
through the choice of good variable names and the use of idiomatic statements.
Classes
In Java, typically, each class is given in a separate file. The beginning
of the file should contain a comment that explaine the what the class is
for. Often, one also puts here information concerning the author, the date
of last modification, and so on. Here is an example appropriate for any
language.
//An object of class Auto represents a car.
//Author: John Doe.
//Date of last modification: 25 August 1998
public static class Car;
A more idiomatic expression of the same specification in Java would
be:
/**
* An object of class Auto represents a car.
* @author John Doe
* @modified 25 August 1998
*/
public static class Car;
Methods
Every method should be preceded by a comment giving its specification.
This specification, together with the heading of the method, which gives
the number and types (classes) of the parameters and the type (class) of
the result, should provide all of the information needed to use the method
--and no more. It should describe any restrictions on the parameters and
what the method does, not how it does it. One should never have to look
at the body of a method to understand how to use it. The specification
comment should describe the parameters of the method. The specifications
can usually be written in few sentences.
Here is an example.
/**
* Print on System.out the most frequently occurring temperature
* in t[0..c-1]. If there is more than one possibility, print
* the least temperature.
*/
void printCommon(Temperature[] t, int c)
Unfortunately, it is more typical to find a comment like the following
(if any comment is provided at all).
/**
* Print most frequent temperature
*/
void printCommon(Temperature[] t, int c)
This specification fails to say what part of array t is to be included
in finding the most frequent temperature. It also fails to say where to
print, which will be a problem if there is more than one possibility. The
only way for the user to find out is to look at the body of the method
(if it is available), and that should not be necessary.
For a function -- i.e. a method that returns a value -- it is often
easiest to simply describe the value returned, using the word "yield":
/**
* Yield distance between points (x1,y1) and (x2,y2)
*/
double dist (int x1, int y1, int x2, int y2)
Variables
Every significant variable and data structure needs a precise and complete
definition, which provides whatever information is needed to understand
the variable The most useful information is often the invariant properties
of the data: facts that are always true except, perhaps, momentarily, when
several related variables are being updated.
Important hint: Write the definition of a set of variables when you
first conceive of using them, and type the definitions as comments into
the program when you type in the declarations of the variables. If you
later decide to change the meanings of the variables, change the definitions
before you change the statement that use the variables.
Here is an example of a definition for two variables i and currentItem
--they are defined in a single comment because they are related.
// 0 <= i <= currentItem < numberItems and i is the smallest
value
// such that item i's price is at most item currentItem's price.
The definition for a boolean variable is usually best presented as the
value of some English (or mathematical) statement. For example, the following
two definitions are equivalent, but the first is shorter.
boolean b; // = " user selected menu item File | Quit"
boolean b; // true if the user selected menu item File | Quit;
//
false otherwise
The more precise a definition, the better. Comments like "flag for loop"
or "index into b" are useless; they only say how the variable is used,
but not what it means.
Related variables should be declared and described together. For example,
the definition of a table should describe not only the array that holds
the data but also the integer variable that contains the number of items
currently in the table. In the example below, for utmost clarity tabs are
used to line up identifier names and to line up the comments.
final int maxTemp = 150; // max number of temperature readings.
int nTemp = 0;
// Temperature readings are in
// temp[0..nTemp-1], where
// 0 <= nTemp < maxTemp
double temps = new double[maxTemp];
If the comment is too long to fit nicely on the right (as is the case
above), then put it above the declarations.
// Temperature readings are in temp[0..nTemp-1], where
// 0 <= nTemp <= maxTemp and maxTemp is the maximum number
of
// temperature readings final int maxTemp = 150;
int nTemp = 0;
double temps = new double[maxTemp]
Finally, it is idiomatic in Java to use javadoc-style comments rather
than double-slash-style comments when defining class variables (fields).
Double-slash-style comments should still be used for local variables.
class List {
/**
* Maximum size of list
*/
static final int maxSize = 100;
}
Statements
Just as the sentences of an essay are grouped in paragraphs, so the sequence
of statements of the body of a method should be grouped into logical units.
Often, the clarity of the program is enhanced by preceding a logical unit
by a comment that explains what it does. This comment serves as the specification
for the logical unit; it should say precisely what the logical unit does.
The comment for such a logical unit is called a "statement-comment".
It should be written as a command to do something. Here is an example.
// Truthify x >= y by swapping x and y if needed.
if (x < y) {
int tmp = x;
x = y;
y = tmp;
}
The comment should explain what the group of statements does, not how
it does it. Thus, it serves the same purpose as the specification of a
method: it allows one to skip the reading of the statements of the logical
unit and just read the comment. With suitable statement-comments in the
body of a method, one can read the method at several "levels of abstraction",
which helps one scan a program quickly to find a section of current interest,
much like one scans section and subsection headings in an article or book.
But this purpose is served only if statement-comments are precise.
Statement comments must be complete. The comment
// Test for valid input
is not adequate. What happens if the input is valid? What if it isn't
-- is an error message written or is some flag set? Without this information,
one must read the statements for which this statement-comment is a specification,
and the whole purpose of the statement comment is lost.
Placement of statement-comments
In the example above, and in the following example, the statements specified
by a statement comment are indented. In a program with several levels of
statement-comments, this indentation is useful in clarifying the structure
of a program. In fact, with several levels of statement-comments, even
judicious use of blank lines cannot eliminate all ambiguity concerning
what statement belongs to what statement-comment. The program fragment
given below illustrates this.
// Truthify the definition of t -- return false if not possible
// Eliminate whitespace from the beginning and
end of t
while (t.length() != 0 && isWhitespace(t.charAt(0)))
t= t.substring(1);
// If t is empty, print an error message and
return
if (t.length() == 0) {
...
return false;
}
if (containsCapitals(t)) {
...
}
// Store the French translation of t in tFrench
...
At the highest level, the program fragment consists of two statements:
(1) Truthify the definition t and (2) Store the French translation of t
in tFrench. The statement "Truthify the definition of t" is implemented
in three steps, two of which are themselves statement-comments. Thus, this
program fragment has three levels of abstraction.
In this example, note how reliance on the definition of t in the
statement-comment "Truthify the definition of t" allows the statement-comment
to be short but precise. Of course, a suitable definition for t must appear
at its declaration.
Many people prefer not to indent substatements of a statement-comment
and to rely on blank lines to separate the end of the substatements of
a statement-comments. Below, we show the above program fragment in this
style. Note the extra comment to help the reader see the end of the substatements
for "truthify the definition of t". This style is has its problems. If
you decide to use this style, then program in such a way that statement-comments
do not appear within statement-comments, so that such awkwardness does
not arise. Modularizing into separate functions is one way to do this.
// Truthify the definition of t --return false if not possible
// Eliminate whitespace from the beginning and end of t
while (t.length() != 0 && isWhitespace(t.charAt(0))) {
t= t.substring(1);
}
// If t is empty, print an error message and return
if (t.length() == 0) {
...
return false;
}
if (containsCapitals(t)) {
...
}
// (End of Truthify the definition of t)
// Store the French translation of t in tFrench
|