Code Documentation & Type Hints

Thomas J. Kennedy

Contents:

Most of your code has probably had quite a few in-line comments. Inline comments are not the focus of this discussion. The focus of this discussion is documentation of classes, functions, and methods.

1 A Few Starting Examples

I work in few different languages. Throughout my

You have definitely been told to “comment your code” in the past, but (probably) in a less formal fashion.

Let us start with a few selected documentation examples from my CS 330 and CS 417 notes.

1.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;

1.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

1.3 Python

Pydoc or Sphinx can be used for Python. Consider the following Pydoc Example:

Example 3: Python 3 Pydoc Documentation
def 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 Documentation
def 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)
    """

1.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.

2 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.

2.1 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).

def one_flip(p):
    return True if random.random() < p else False


def main():

    num_flips = 8;

    for _i in range(0, num_flips):
        if one_flip(0.7):
           print("Heads")

        else:
            print("Tails")

if __name__ == "__main__":
    main()

The one_flip function needs a description.

def one_flip(p):
    """
    Simulate a single coin flip.
    """

What does p represent? Does it represent the probability of heads or tails?

def one_flip(p):
    """
    Simulate a single coin flip.

    Args:
        p: probability of heads in the range [0, 1]
    """

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.

def one_flip(p):
    """
    Simulate a single coin flip.

    Args:
        p: probability of heads in the range [0, 1]

    Returns:
        True if the result is heads and False if the result is tails
    """

There is no more ambiguity or guesswork. Both p and the possible return values are documented.

2.2 Function Type Hints

I am a stickler for type hints…

def one_flip(p: float) -> bool:
    """
    Simulate a single coin flip.

    Args:
        p: probability of heads in the range [0, 1]

    Returns:
        True if the result is heads and False if the result is tails
    """

Now… we know that

As general rules, forgo:

It is possible to annotate variables. However, variables generally have more context, e.g.,