Grade by Grade
A. Programming Concepts and Skills · Data Types, Files and Expressions

Code Maintenance: Documenting Code to Industry Standards

A4.3 A4.1About 65 minutes
Learning goals
  • I can write module, class and function docstrings that follow PEP 257 and a standard format such as Google style.
  • I can use type hints, clear names and PEP 8 conventions as part of documentation.
  • I can write block and line comments that explain why, and remove comments that only repeat the code.
  • I can use doctests to keep examples in documentation correct, and compare Python docstrings with Java doc comments.

Words to know

internal documentation
Documentation inside the source code (docstrings, comments, names) written for programmers.
docstring
A string literal as the first statement of a module, class or function; Python stores it and shows it with help().
PEP 8 / PEP 257
Python Enhancement Proposals giving the community's style guide (PEP 8) and docstring conventions (PEP 257).
block comment
One or more full-line # comments placed before the code they explain.
inline (line) comment
A short # comment at the end of a line of code, separated by at least two spaces.
type hint
An annotation such as cents: int or -> int that documents expected types; checked by tools, not by Python when running.
doctest
An example in a docstring written as >>> lines with expected output, which the doctest module can run as a test.
doc comment (Javadoc)
Java's documentation comment, which begins with a slash and two asterisks and sits above a class or method, with tags like @param and @return.

Code is read far more often than it is written

A program you write this week might be changed next year by a teammate, by a co-op student, or by you after you have forgotten every detail. Professional teams therefore follow documentation standards, so that anyone can understand any file quickly.

The curriculum asks for "fully documented program code according to industry standards", naming doc comments, docstrings, block comments and line comments. In ICS3U you wrote header comments and simple docstrings. This lesson raises the bar to the conventions used in industry and open-source Python projects.

users of the programuser manual, help screens(external docs, sibling unit)programmers calling itdocstrings + type hintshelp(), IDE pop-ups, generated docsprogrammers editing itblock and line commentsread next to the code itself
Different readers need different documentation. Docstrings describe what a function does and how to call it; comments inside the code explain how and why for the next person who edits it. User manuals are external documentation (expectation A4.4), covered in the sibling units.

The Python standards

  • PEP 8 (style): snake_case for functions and variables, CapWords for classes, UPPER_CASE for constants, 4-space indentation, lines of at most 79 characters, and comments that are complete sentences.
  • PEP 257 (docstrings): every public module, class and function gets a docstring in triple double quotes. The first line is a short summary ending in a period, written as a command ("Return the…", not "Returns the…" or "This function…"). For more detail, leave a blank line and continue.
  • A docstring format for the details. Teams pick one and use it everywhere. Google style uses Args:, Returns: and Raises: sections; NumPy style and reStructuredText (:param x:) are other common choices.
  • Type hints (cents: int, -> int, list[float]) document types in a way tools can check.
"""Cash rounding for Canadian prices."""module docstring: purposeTAX_RATE = 0.13  # Ontario HSTinline comment: a factdef cash_round(cents: int) -> int:type hints: in and out    """Return a cash total rounded...docstring: summary line,    Args: ... Returns: ... Raises: ..."""then standard sections    # Add 2 so that 3, 4, 8, 9 round up.block comment: explains WHY
The layers of internal documentation in one small file: a module docstring, comments on constants, type hints, a function docstring with a summary line and structured details, and block comments that explain why the code does something non-obvious.
"""Cash rounding for Canadian prices.
Since the penny was withdrawn in 2013, cash totals in Canada are rounded
to the nearest 5 cents. Card payments are not rounded.
"""
def cash_round(cents: int) -> int:
"""Return a cash total rounded to the nearest 5 cents.
Totals ending in 1, 2, 6 or 7 cents round down; totals ending in
3, 4, 8 or 9 cents round up.
Args:
cents: The total in whole cents. Must not be negative.
Returns:
The rounded total in whole cents (a multiple of 5).
Raises:
ValueError: If cents is negative.
>>> cash_round(1997)
1995
>>> cash_round(1998)
2000
"""
if cents < 0:
raise ValueError("cents must not be negative")
# Adding 2 before floor-dividing by 5 moves endings 3, 4, 8, 9
# past the next multiple of 5, so they round up; 1, 2, 6, 7 do not.
return (cents + 2) // 5 * 5
if __name__ == "__main__":
import doctest
print(doctest.testmod())
print([cash_round(c) for c in [101, 102, 103, 104, 106, 108]])

A module documented to a common industry standard: PEP 257 docstrings in Google style (Args, Returns, Raises), type hints, and a block comment that explains the one clever line. The >>> examples are doctests: doctest.testmod() runs them and checks the results, so the documentation can never quietly go out of date. Output, line by line: TestResults(failed=0, attempted=2) | [100, 100, 105, 105, 105, 110]

"""Cash rounding for Canadian prices.
Since the penny was withdrawn in 2013, cash totals in Canada are rounded
to the nearest 5 cents. Card payments are not rounded.
"""
def cash_round(cents: int) -> int:
"""Return a cash total rounded to the nearest 5 cents.
Totals ending in 1, 2, 6 or 7 cents round down; totals ending in
3, 4, 8 or 9 cents round up.
Args:
cents: The total in whole cents. Must not be negative.
Returns:
The rounded total in whole cents (a multiple of 5).
Raises:
ValueError: If cents is negative.
>>> cash_round(1997)
1995
>>> cash_round(1998)
2000
"""
if cents < 0:
raise ValueError("cents must not be negative")
# Adding 2 before floor-dividing by 5 moves endings 3, 4, 8, 9
# past the next multiple of 5, so they round up; 1, 2, 6, 7 do not.
return (cents + 2) // 5 * 5
help(cash_round)

Because the documentation is a docstring, help(), IDE pop-ups and documentation generators such as Sphinx all show it automatically. A # comment would not appear here.

Comments: explain why, not what

A comment that says what the code obviously does (x += 1 # add 1 to x) is clutter, and it becomes a lie the day someone changes the code but not the comment. Good comments explain what the code cannot say:

  • Why a choice was made: # Add 2 so that endings 3, 4, 8, 9 round up.
  • Facts from outside the program: units, sources, rules: TAX_RATE = 0.13 # Ontario HST.
  • Warnings and limits: # Assumes the list is already sorted.
  • TODO notes for known unfinished work, ideally with a name or issue number.

PEP 8 details: block comments start with # and are indented like the code they describe; inline comments are separated from the code by at least two spaces. Often the best fix for a confusing line is a better name or a small, well-named helper function rather than a comment.

def f(l, t):
# loop
c = 0
for x in l: # for each x in l
if x > t:
c += 1 # add 1 to c
return c
print(f([3, -2, 7, 0], 0))

Code that works but is badly documented: meaningless names (f, l, t, c), no docstring, and comments that only repeat the code. Example 1 rewrites it. Output: 2

Worked example

Example 1: Bring code up to standard

Rewrite the function above so that it meets industry documentation standards (PEP 8 names, a PEP 257 docstring, type hints, and only useful comments). It counts how many readings are above a threshold.

  1. Names first. Good names remove the need for many comments: count_above(readings, threshold), with a loop variable value and a counter count.
  2. Type hints state what goes in and out: def count_above(readings: list[float], threshold: float) -> int:.
  3. Docstring: a one-line summary in the imperative mood ("Return the number of…"), a blank line, then Args and Returns. Mention edge cases: values equal to the threshold are not counted.
  4. Comments: delete # loop, # for each x in l and # add 1 to c. They repeat the code and add nothing. Keep a comment only if it explains why (none is needed here).
  5. Test the docs: add a doctest such as >>> count_above([3, -2, 7, 0], 0) with the result 2.

A function named count_above with type hints, a docstring stating that equal values are not counted, a doctest, and no redundant comments.

A documentation checklist

Before you hand in (or commit) code, check: every file starts with a module docstring (purpose, and the author and date if your teacher or team requires them); every public function and class has a docstring with a summary line and its parameters, return value and exceptions; names follow PEP 8; type hints are on function signatures; comments explain why; no commented-out dead code remains; and doctests pass.

Worked example

Example 2: The same function documented in Java

Show how the cash_round documentation would look as a Java doc comment, and compare it with the Python docstring.

  1. Java uses Javadoc comments, which begin with a slash and two asterisks and sit above the method. Python docstrings sit inside, as the first statement.
  2. The summary sentence comes first, then tags for the details: @param for each parameter, @return for the result and @throws for each exception. The Java block below shows the whole comment.
  3. The javadoc tool turns these comments into web pages, just as Sphinx or pydoc does for Python docstrings.
  4. The idea is the same in both languages: structured documentation, kept in the source file, that tools can read.

Python: a docstring inside the function with Args, Returns and Raises sections. Java: a doc comment above the method with @param, @return and @throws tags.

/**
* Returns a cash total rounded to the nearest 5 cents.
*
* @param cents the total in whole cents; must not be negative
* @return the rounded total in whole cents (a multiple of 5)
* @throws IllegalArgumentException if cents is negative
*/
public static int cashRound(int cents) {
if (cents < 0) {
throw new IllegalArgumentException("cents must not be negative");
}
return (cents + 2) / 5 * 5; // non-negative, so / acts like Python's //
}

The same function in Java with a Javadoc comment (Java code, shown for comparison; it is not run in this course). Java's / on two ints truncates toward zero, which matches Python's // here because the value is never negative (Lesson 1).

Just for fun

Programmers have a saying: "Code tells you how; comments tell you why." Without them, every program eventually becomes a mystery novel in which the author is also the main suspect.

Hands-on activity

Documentation makeover

You need: Python and one program you wrote earlier this year (or in ICS3U) that is at least 30 lines long.

Step 1. Before changing anything, ask someone at home to read your code for two minutes and tell you what they think it does. Write down what they found confusing.

Step 2. Add a module docstring, a docstring for every function (Google style), type hints on every function signature, and constants in UPPER_CASE.

Step 3. Delete comments that repeat the code; add comments that explain why for anything tricky. Rename unclear variables.

Step 4. Add at least two doctests and run python -m doctest -v yourfile.py. Then run help() on your functions to see your documentation as other programmers will.

Step 5. Ask the same person to read it again. Did it make more sense?

What to look for: imperative summary lines, Args/Returns/Raises where needed, no redundant comments, doctests that pass, and a reader who understands more the second time.

Your turn

1

Rewrite this summary line to follow PEP 257: """This function will calculate the area of a circle"""

2

Which comment is worth keeping? (a) total = 0 # set total to 0 (b) total = 0 # in cents, to avoid float rounding

3

Write the Google-style Args: and Returns: sections for def bmi(mass_kg: float, height_m: float) -> float:.

4

How many spaces does PEP 8 require before an inline # comment?

5

Where does a Python docstring go, and where does a Java doc comment go?

6

What does doctest.testmod() do?

7

Name the three things PEP 8 says about naming functions, classes and constants.

More practice: warm-up

1

What kind of quotes should a docstring use?

2

What should the first line of a docstring be?

3

Which built-in shows a function's docstring?

4

What does -> int after a function's parameters mean?

5

Which symbol starts a Python comment?

More practice: on your own

1

Write a complete docstring (summary, Args, Returns, Raises, one doctest) for def to_base(n: int, base: int) -> str: from Lesson 2.

2

Explain why commented-out code (old lines turned into comments) should usually be deleted before submitting.

3

A function has a correct docstring, but a later change made it return None instead of 0 for an empty list, and the docstring still says 0. Suggest a practice that would have caught this.

4

Rename and document: def calc(a, b): return a * b / 100, used to find a tip amount from a bill and a percentage.

5

Why is a type hint like def area(r: float) -> float: documentation and not a guarantee?

Challenge

1

Write the Javadoc comment for a Java method public static int gcd(int a, int b) that uses Euclid's algorithm and throws an exception if both are 0. Follow the layout of the Java block in this lesson.

2

A teammate says, "Good code doesn't need comments." Give one way they are right and one way they are wrong.

3

Design a team documentation standard for a 4-person Python project in five rules, and say how you would check rule compliance automatically.

Check your understanding

Quiz

1.

Which docstring summary line best follows PEP 257?

2.

Which comment is most useful?

3.

What is a doctest?

4.

In Java, which tag documents a method's parameter?

5.

According to PEP 8, how should a constant be named?

6.

Why put documentation in a docstring rather than a # comment above the function?

For parents and teachers

Big idea: A4.3 asks students to create fully documented program code according to industry standards (doc comments, docstrings, block comments, line comments). The help() and doctest work also supports A4.1. Most programs in this unit have been documented this way, so this lesson names and formalizes the habits.

How to help: step 1 of the activity needs you. Read your teen's code for two minutes, before and after the makeover, and honestly say what confused you. You do not need to know Python: if a non-programmer can follow the docstrings, they are good.

Common difficulties:

  • Comments that repeat the code instead of explaining why.
  • Docstrings that describe the implementation ("loops through the list") instead of the behaviour and the parameters.
  • Forgetting to update documentation after changing code (doctests help).
  • Treating type hints as if Python enforced them.