Skip to content

Commit 898b418

Browse files
authored
Merge pull request #348 from KempnerInstitute/238-feature-documentation-and-readability
2 parents 8437051 + 06a40e8 commit 898b418

3 files changed

Lines changed: 177 additions & 2 deletions

File tree

kempner_computing_handbook/s2_swe_for_research/documentation_and_readibility.md

Lines changed: 177 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,195 @@
22

33
This section focuses on best practices and tools that improve documentation and code clarity in research software.
44

5-
Why Does Documentation Matter?
5+
**Why does documentation matter?**
66
- Clear documentation ensures reproducibility, collaboration, and long-term usability.
7-
- Readable code is easier to debug, extend, and validate - critical for research integrity.
7+
- Readable code is easier to debug, extend, and validate, which is critical for research integrity.
88

9+
(documentation_and_readibility:types_of_documentation)=
910
## Types of Documentation
1011

12+
Documentation is not a single thing: different types serve different reader needs, and matching the type to the need is what keeps docs useful. A widely used way to organize these needs is the [Diátaxis](https://diataxis.fr/) framework, which separates documentation into four kinds:
13+
14+
- **Tutorials (learning-oriented):** lessons that take a newcomer through a hands-on experience to build basic competence.
15+
- **How-to guides (task-oriented):** practical, step-by-step directions that help an already-competent user accomplish a specific goal.
16+
- **Reference (information-oriented):** accurate, lookup-friendly descriptions of the machinery, such as APIs, parameters, and configuration.
17+
- **Explanation (understanding-oriented):** background and discussion that clarifies concepts and answers "why" questions.
18+
19+
```{figure} figures/png/diataxis_map.png
20+
---
21+
width: 85%
22+
name: diataxis-map
23+
---
24+
The Diátaxis map organizes the four documentation types along two axes: action versus cognition, and acquiring versus applying skill. (*Credit: [Daniele Procida / diataxis.fr](https://diataxis.fr/), CC BY-SA 4.0*)
25+
```
26+
27+
Most research projects do not need all four as separate manuals, but they usually combine a few concrete artifacts:
28+
29+
- **README (the front door):** the first file a reader opens. It should state what the project is, how to install it, and a minimal usage example, then point to anything deeper.
30+
- **Docstrings and API reference:** per [PEP 257](https://peps.python.org/pep-0257/), a docstring is a string literal placed as the first statement in a module, function, class, or method. Docstrings on public functions and classes are the source of reference documentation and are accessible at runtime via `__doc__`.
31+
- **Inline comments:** explain *why*, not *what*. The code already shows what it does; comments should capture intent, assumptions, and non-obvious reasoning.
32+
- **Runnable examples and tutorials:** small scripts or notebooks a reader can execute to see the project work end to end.
33+
34+
A minimal README outline that covers the essentials:
35+
36+
```markdown
37+
# Project Name
38+
One-sentence description of what it does and why.
39+
40+
## Installation
41+
How to set up the environment and install dependencies.
42+
43+
## Usage
44+
A minimal, runnable example.
45+
46+
## License / Citation
47+
How to reuse and how to cite.
48+
```
49+
50+
```{tip}
51+
Keep the README short and link out for depth. For broader guidance on documenting research software, see [The Turing Way](https://book.the-turing-way.org/) and [Write the Docs](https://www.writethedocs.org/).
52+
```
53+
54+
(documentation_and_readibility:code_readability_best_practices)=
1155
## Code Readability Best Practices
1256

57+
Readable code is documentation in itself: it lowers the cost of every later read, debug, and change. As [PEP 8](https://peps.python.org/pep-0008/) notes, code is read far more often than it is written, and the [Zen of Python](https://peps.python.org/pep-0020/) puts it plainly: "Readability counts."
58+
59+
- **Use meaningful, descriptive names.** Names should reveal intent so the reader does not have to decode them. Avoid ambiguous abbreviations and single-letter names except for short-lived loop counters.
60+
- **Follow a consistent style guide.** For Python, [PEP 8](https://peps.python.org/pep-0008/) is the common baseline: `snake_case` for functions and variables, `UPPER_CASE` for constants, and lines limited to 79 characters (teams may agree to extend to 99). Consistency within a project matters more than any single rule.
61+
- **Keep functions small and focused.** A function should do one thing. The [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html) suggests reconsidering a function once it grows past roughly 40 lines, since smaller functions are easier to read, test, and reuse.
62+
- **Reduce deep nesting with guard clauses.** "Flat is better than nested" (Zen of Python). Return early on invalid or edge cases so the main logic stays at a shallow indentation level.
63+
- **Let comments explain why, not what.** The code already shows what it does; comments should capture intent and non-obvious reasoning. See {ref}`Types of Documentation <documentation_and_readibility:types_of_documentation>` for the role of inline comments.
64+
65+
A small refactor that applies clear names plus an early-return guard clause:
66+
67+
```python
68+
# Before: cryptic names and deep nesting
69+
def p(d):
70+
if d:
71+
if d > 0:
72+
return d * 0.9 # what is 0.9?
73+
return None
74+
75+
# After: descriptive names and a guard clause
76+
DISCOUNT_RATE = 0.9 # 10% loyalty discount
77+
78+
def apply_discount(price):
79+
if price is None or price <= 0:
80+
return None
81+
return price * DISCOUNT_RATE
82+
```
83+
84+
```{tip}
85+
You do not have to apply a style guide by hand. Automated formatters and linters can enforce these conventions for you; they are covered in the next section.
86+
```
87+
88+
(documentation_and_readibility:tools_and_practices)=
1389
## Tools and Practices
1490

91+
A small toolchain turns the docstrings described in {ref}`Types of Documentation <documentation_and_readibility:types_of_documentation>` into browsable, searchable documentation, and keeps the examples inside them honest as the code changes.
92+
93+
- **Pick a docstring style and stay consistent.** [PEP 257](https://peps.python.org/pep-0257/) defines what a docstring is and the basic one-line and multi-line conventions, but not how to lay out arguments and return values. The two common structured styles are [Google style](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings) and [NumPy style](https://numpydoc.readthedocs.io/en/latest/format.html). Either works; the goal is to use one consistently across a project.
94+
- **Generate docs from docstrings.** [Sphinx](https://www.sphinx-doc.org/) builds documentation directly from your code: the [autodoc](https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html) extension pulls in docstrings, and the [napoleon](https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html) extension lets autodoc understand Google- and NumPy-style docstrings. If your project documents in Markdown, [MkDocs](https://www.mkdocs.org/) with the [mkdocstrings](https://mkdocstrings.github.io/) plugin does the same job.
95+
- **Test the examples with doctest.** Python's [doctest](https://docs.python.org/3/library/doctest.html) module finds `>>>` examples in docstrings, runs them, and checks the output against what you wrote, so examples cannot silently drift out of date.
96+
- **Add type hints as checked interface documentation.** [PEP 484](https://peps.python.org/pep-0484/) annotations record the expected argument and return types in a form that a static checker can verify, documenting the interface without separate prose. Note that these are not enforced at runtime.
97+
- **Host the built docs.** [Read the Docs](https://docs.readthedocs.io/) builds and hosts Sphinx or MkDocs sites automatically from your Git repository, rebuilding on each push so the published docs track the code.
98+
99+
A NumPy-style docstring with type hints and an embedded doctest:
100+
101+
```python
102+
def normalize(values: list[float]) -> list[float]:
103+
"""Scale values so they sum to 1.
104+
105+
Parameters
106+
----------
107+
values : list[float]
108+
Non-empty list of non-negative numbers.
109+
110+
Returns
111+
-------
112+
list[float]
113+
The input values divided by their total.
114+
115+
Examples
116+
--------
117+
>>> normalize([1.0, 1.0, 2.0])
118+
[0.25, 0.25, 0.5]
119+
"""
120+
total = sum(values)
121+
return [v / total for v in values]
122+
```
123+
124+
```bash
125+
# Run the embedded examples; no output means every example passed.
126+
python -m doctest example.py
127+
```
128+
129+
```{tip}
130+
Keeping a `>>> ` example in the docstring means the same snippet documents the function and serves as a regression test. Writing readable code in the first place (see {ref}`Code Readability Best Practices <documentation_and_readibility:code_readability_best_practices>`) makes that documentation shorter and clearer.
131+
```
132+
133+
(documentation_and_readibility:documentation_in_research_context)=
15134
## Documentation in Research Context
16135

136+
Research code carries documentation needs beyond general software: the documentation must let others, and your future self, understand the work, reproduce a result, and cite it correctly.
137+
138+
- **Document methods, parameters, and assumptions.** Record the method, the parameter values and ranges, random seeds, software versions, and any assumptions a result depends on, so the result can be regenerated rather than guessed at. This is the heart of reproducibility; for the full treatment see the [Reproducible Research](reproducible_research.md) chapter.
139+
- **Document the data.** Ship a dataset README or a data dictionary (also called a codebook) that lists each variable with its meaning, units, allowed values, and provenance. The [Turing Way](https://book.the-turing-way.org/reproducible-research/rdm/rdm-metadata/) calls a data dictionary one of the most important pieces of documentation in a study. At a high level, aim for [FAIR](https://www.go-fair.org/fair-principles/) data: Findable, Accessible, Interoperable, and Reusable.
140+
- **Make the work citable.** Add a [`CITATION.cff`](https://citation-file-format.github.io/) file to the repository root. It is a small YAML file that tools can read, and GitHub uses it to add a "Cite this repository" link and to offer APA and BibTeX citations (see GitHub's [about-citation-files](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-citation-files)). To get a citable, versioned DOI, archive a release with a service such as Zenodo, then record that DOI in the file.
141+
142+
```{figure} figures/png/fair_principles.png
143+
---
144+
width: 90%
145+
name: fair-principles
146+
---
147+
The FAIR guiding principles for research data: Findable, Accessible, Interoperable, and Reusable. (*Credit: [SangyaPundir / Wikimedia Commons](https://commons.wikimedia.org/wiki/File:FAIR_data_principles.svg), CC BY-SA 4.0*)
148+
```
149+
150+
A minimal, valid `CITATION.cff`:
151+
152+
```yaml
153+
cff-version: 1.2.0
154+
message: "If you use this software, please cite it as below."
155+
title: "Example Analysis Toolkit"
156+
authors:
157+
- family-names: Smith
158+
given-names: Jane
159+
version: 1.0.0
160+
doi: 10.5281/zenodo.1234567 # DOI for the archived release
161+
```
162+
163+
```{tip}
164+
Update the `version` and `doi` each time you archive a new release, so a citation points to the exact version that produced a result.
165+
```
166+
167+
(documentation_and_readibility:mental_models_for_readers)=
17168
## Mental Models for Readers
18169

170+
As someone reads your code or docs, they build a [mental model](https://www.nngroup.com/articles/mental-models/) of how it works: an internal picture of the moving parts and how they fit together. Your job is to help them build an accurate one quickly. Clear code and docs do this for you; surprising or unexplained code forces the reader to reverse-engineer your intent.
171+
172+
- **Beware the curse of knowledge.** The [curse of knowledge](https://en.wikipedia.org/wiki/Curse_of_knowledge) is the bias that, once you know something, you assume others share that context. What is obvious to you as the author is not obvious to a newcomer or to your future self, so write for a reader who lacks your background and spell out the assumptions you take for granted.
173+
- **Follow the principle of least astonishment.** A component should [behave the way most readers expect](https://en.wikipedia.org/wiki/Principle_of_least_astonishment), so names and behavior should match conventions and hold no hidden surprises. A function named like a pure lookup should not quietly write a file or mutate its input. See {ref}`Code Readability Best Practices <documentation_and_readibility:code_readability_best_practices>` for naming that reveals intent.
174+
- **Use progressive disclosure.** [Progressive disclosure](https://www.nngroup.com/articles/progressive-disclosure/) means leading with the few most important things and deferring the rest. Put the common case and a high-level overview first, and push details, options, and edge cases lower so a reader is not flooded before they have the big picture.
175+
- **Lead with the why.** Before the mechanics, give a short conceptual overview of what the code is for and why it exists. This is the explanation type from {ref}`Types of Documentation <documentation_and_readibility:types_of_documentation>`, and it is the context a reader needs to interpret everything that follows.
176+
177+
A name that matches behavior keeps the reader's mental model accurate:
178+
179+
```python
180+
# Astonishing: the name implies a read-only lookup, but it mutates input
181+
def get_user(users, index):
182+
users.sort() # surprising side effect hidden behind a "get"
183+
return users[index]
184+
185+
# Unsurprising: the name matches what the function actually does
186+
def find_user(users, index):
187+
return users[index] # pure lookup, no side effects
188+
```
189+
190+
```{tip}
191+
A quick check: skim your README or module top-down and ask whether a reader who has never seen the project would, after the first few lines, know what it does and why. If not, add a short overview before the details.
192+
```
193+
19194
## Summary Checklist
20195

21196
- [ ] Docstrings on all public functions/classes
76.6 KB
Loading
32.2 KB
Loading

0 commit comments

Comments
 (0)