|
2 | 2 |
|
3 | 3 | This section focuses on best practices and tools that improve documentation and code clarity in research software. |
4 | 4 |
|
5 | | -Why Does Documentation Matter? |
| 5 | +**Why does documentation matter?** |
6 | 6 | - 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. |
8 | 8 |
|
| 9 | +(documentation_and_readibility:types_of_documentation)= |
9 | 10 | ## Types of Documentation |
10 | 11 |
|
| 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)= |
11 | 55 | ## Code Readability Best Practices |
12 | 56 |
|
| 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)= |
13 | 89 | ## Tools and Practices |
14 | 90 |
|
| 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)= |
15 | 134 | ## Documentation in Research Context |
16 | 135 |
|
| 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)= |
17 | 168 | ## Mental Models for Readers |
18 | 169 |
|
| 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 | + |
19 | 194 | ## Summary Checklist |
20 | 195 |
|
21 | 196 | - [ ] Docstrings on all public functions/classes |
|
0 commit comments