Code Documentation & Comments
Thomas J Kennedy
Most of your code in CS 150 and CS 250 probably had quite a few comments. Inline comments are not the focus of this discussion. The focus of this discussion is documentation of classes, functions, and methods.
1 Beginning of the Semester
If you are reading these notes at the beginning of the semester… skim. We will revisit this discussion later this semester.
2 A Few Starting Examples
I work in few different languages. Throughout my
- C++ code you will find Doxygen style comments.
- Java code you will find Javadoc style comments.
- Python code you will find Pydoc style comments.
- Rust code you will find Rustdoc style comments.
You have definitely been told to “comment your code” in the past, but in a less formal fashion.
Let us start with a few selected documentation examples from my CS 330 and CS 417 notes.
2.1 C++
Doxygen can be used for C++. Consider the following Doxygen Example:
Example 1: C++ Doxygen Documentation/** * Retrieve the value stored in three selected Cells * * @param cell1Id numeric id representing the 1st desired cell * @param cell2Id numeric id representing the 2nd desired cell * @param cell3Id numeric id representing the 3rd desired cell * * @return value stored in the Cell * * @pre (cell1Id > 0 && cell1Id < 10) && * (cell2Id > 0 && cell2Id < 10) && * (cell3Id > 0 && cell3Id < 10) */ CellTriple get3Cells(int cell1Id, int cell2Id, int cell3Id) const;
2.2 Java
Javadoc can be used for Java. Consider the following Javadoc Example:
Example 2: Javadoc Documentation/** * Multi-thread Coin Flip. * * @param numTrials # flips to simulate * @param numThreads number of threads to use * * @return Completed FlipTasks * * @throws InterruptedException if a thread is stopped prematurely */ public static FlipTask[] multiThread(long numTrials, int numThreads) throws InterruptedException
2.3 Python
Pydoc or Sphinx can be used for Python. Consider the following Pydoc Example:
Example 3: Python 3 Pydoc Documentationdef parse_raw_temps(original_temps: TextIO, step_size: int=30, units: bool=True) -> Iterator[Tuple[float, List[float]] ]: """ Take an input file and time-step size and parse all core temps. :param original_temps: an input file :param step_size: time-step in seconds :param units: True if the input file includes units and False if the file includes only raw readings (no units) :yields: A tuple containing the next time step and a List containing _n_ core temps as floating point values (where _n_ is the number of CPU cores) """
I prefer the Sphinx/Google style for Python.
Example 4: Python 3 Sphinx/Google Style Documentationdef parse_raw_temps(original_temps: TextIO, step_size: int=30, units: bool=True) -> Iterator[Tuple[float, List[float]] ]: """ Take an input file and time-step size and parse all core temps. Args: original_temps: an input file step_size: time-step in seconds units: True if the input file includes units and False if the file includes only raw readings (no units) Yields: A tuple containing the next time step and a List containing _n_ core temps as floating point values (where _n_ is the number of CPU cores) """
2.4 Rust
Example 5: Rust Documentation/// /// Take a room and change the flooring /// /// # Arguments /// /// * `original` - House to change /// /// # Returns /// /// House with the updated flooring /// fn upgrade_flooring(original: &House) -> House { //... }
Rust and Python have similar documentation styles (give or take some markdown
formatting). Since we only cover small snippets of Rust in this course (for context), we will forgo a complete Rustdoc discussion.
3 Writing Good Documentation
All code should be properly and fully documented using a language appropriate comment style. All functions (including parameters and return types) must be documented.
3.1 Auditing the C++ Cookbook
If you took CS 350 (Introduction to Software Engineering) recently.. you may recall my Cookbook Example from the Mutator-Accessor Unit Testing Notes. In CS 330 (Object Oriented Programming and Design)… we will use the C++ Cookbook and Java Cookbook as running examples.
Let us start with a quick audit of a C++ Cookbook Example.
The second constructor looks incomplete.
/**
* Create a Cookbook that can contain at most _r_
* recipes.
*/
Cookbook(int r);
Notice the int r
? It is not documented. What does r
represent? Let us start by adding @param r
to the documentation
/**
* Create a Cookbook that can contain at most _r_
* recipes.
*
* @param r max number of recipes that can be stored.
*/
Cookbook(int r);
That is a little more clear. The description should probably be cleaned up a little.
/**
* Create a Cookbook with a set recipe limit.
*
* @param r max number of recipes that can be stored.
*/
Cookbook(int r);
What did we do?
- We documented the input into the constructor (i.e., the parameter
r
) with an@param
tag. - We revised the description for brevity and clarity.
Any time a function has a parameter it should be documented. Should, not must. There are a few exceptions.
3.2 Auditing the Java Cookbook
Let us perform a quick audit of a Java Cookbook Example.
Similar to the C++ Cookbook… the second constructor looks incomplete.
/**
* Create a Cookbook that can contain at most _r_
* recipes.
*/
public Cookbook(int r);
The int r
is not documented. What does r
represent? Let us by add @param r
to the documentation and clean up the description.
/**
* Create a Cookbook with a set recipe limit.
*
* @param r max number of recipes that can be stored.
*/
public Cookbook(int r);
What did we do?
- We documented the input into the constructor (i.e., the parameter
r
) with an@param
tag. - We revised the description for brevity and clarity.
This feels like Déjà vu. We just updated the Java Constructor using the same process from the C++ version!
There are a few more tweaks to be made to the Java Cookbook documentation. However, I will leave those tweaks as a practice exercise.
3.3 Documentation for a New Function
Suppose we have just finished writing a quick program to simulate a trick coin (i.e., a coin where heads and tails are not equally probable).
bool one_flip(double p);
int main(int argc, char** argv)
{
// Seed the random number generator with the current time
srand(time(NULL));
const int num_flips = 8;
for (int i = 0; i < num_flips; i++) {
if (one_flip(0.7)) {
cout << "Heads" << "\n";
}
else {
cout << "Tails" << "\n";
}
}
return 0;
}
The one_flip
function needs a description.
/**
* Simulate a single coin flip.
*/
What does p
represent? Does it represent the probability of heads or tails?
/**
* Simulate a single coin flip.
*
* @param p probability of heads
*/
Now what about the return? We know that bool
means a true
or false
. Which one do I get for heads? Let us add an @return
.
/**
* Simulate a single coin flip.
*
* @param p probability of heads
*
* @return true if the result is heads and false if the result is tails
*/
bool one_flip(double p);
There is no more ambiguity or guesswork. Both p
and the possible return values are documented.
3.3.1 What about Java?
Java and C++ are similar. If we change bool
to boolean
, and rename one_flip
to oneFlip
… we have a Java function.
/**
* Simulate a single coin flip.
*
* @param p probability of heads
*
* @return true if the result is heads and false if the result is tails
*/
boolean oneFlip(double p)
{
//...
}
Notice how the documentation is identical? The basic Javadoc comment syntax overlaps Doxygen comment syntax.