write-docstring-first
When to Use
- Writing any new function
- When clarity of purpose matters
- Complex logic that needs explanation
- Functions that will be reused
- Teaching or educational code
When NOT to Use
- Trivial one-liners (
return x + 1) - Private implementation details
- Rapidly prototyping (add later)
The Pattern
Docstring Fixpoint Theory: Iterate between docstring and code until they are isomorphic - each reads like a translation of the other.
# Step 1: Write docstring as specification
def rainfall(numbers):
"""Return the mean of the non-negative values in a list,
up to the first -999 (if it shows up)."""
...
# Step 2: Write code that mirrors the docstring
def rainfall(numbers):
"""Return the mean of the non-negative values in a list,
up to the first -999 (if it shows up)."""
return mean(non_negative(upto(-999, numbers)))
# Notice: docstring and code are almost the same sentence!
Example (from pytudes DocstringFixpoint.ipynb)
# The Rainfall Problem - evolved through fixpoint iteration
# Version 1: Problem statement as docstring
def rainfall(numbers):
"""Design a program called rainfall that consumes a list of numbers
representing daily rainfall amounts. The list may contain -999
indicating end of data. Produce the average of non-negative values
up to the first -999."""
...
# Version 2: Simplified docstring
def rainfall(numbers):
"""Produce the average of the non-negative values in a list,
up to the first -999 (if it shows up)."""
...
# Version 3: Code mirrors docstring
def rainfall(numbers):
"""Return the mean of the non-negative values in a list,
up to the first -999 (if it shows up)."""
return mean(non_negative(upto(-999, numbers)))
# Helper functions (each with its own fixpoint)
def upto(sentinel, items):
"""Return items that appear before sentinel,
or all items if sentinel doesn't appear."""
return items[:items.index(sentinel)] if sentinel in items else items
def non_negative(numbers):
"""The numbers that are >= 0."""
return [x for x in numbers if x >= 0]
Key Principles
- Docstring first: Specification before implementation
- Iterate both ways: Edit docstring, then code, then docstring...
- Match vocabulary: Use same words in both
- Helpers inherit pattern: Each has its own docstring-code match
- Fixpoint = done: When neither needs changing, you're done