flowchart LR A[Source change] --> B[Human review] B --> C[Formatter] C --> D[Linter] D --> E[Type checker] E --> F[Behavior tests] F --> G[Reviewable result]
Code Quality and Maintainability Overview
1. Two programs can return the same answer and still differ in quality
The Midnight Museum Quest awards points to teams that solve hidden clues. These two functions return the same score for the supplied attempt:
def s(x):
return x[0] * 30 + (25 if x[2] else 0) - max(0, x[1] - 45) * 2
def score_attempt(clues_found, elapsed_minutes, found_bonus_clue):
clue_points = clues_found * 30
bonus_points = 25 if found_bonus_clue else 0
overtime_minutes = max(0, elapsed_minutes - 45)
overtime_penalty = overtime_minutes * 2
return clue_points + bonus_points - overtime_penalty
assert s((6, 42, True)) == 205
assert score_attempt(6, 42, True) == 205The first version is shorter. The second exposes the represented values, their units, and the stages of the rule. Now imagine that overtime becomes three points per minute, but only after minute 50. Which version can you verify more quickly? Which names would help you find every affected line? What behavior must stay unchanged for attempts completed before minute 50?
Code quality is not a beauty contest. It is the practical cost of answering questions like those accurately. In this unit, you will use human reading, static tools, tests, and small diffs as different kinds of evidence. No single check proves that software is correct or maintainable.
Notice that each check answers a different question before a change is accepted.
2. Questions this unit will help you answer
By the end of the unit, you should be able to answer these with code, command output, or a focused explanation:
- Which names, boundaries, and intermediate values make a change safer to review?
- What does an annotation communicate, and what still needs runtime validation?
- How did MyPy or Ruff reach a diagnostic, and should the source or the configuration change?
- Why did a pre-commit hook modify, skip, pass, or fail a file?
- Can a clean CI runner reproduce the same quality result as your local project?
- How can you improve structure one step at a time without silently changing observable behavior?
You will answer them while improving a small src-layout project called Midnight Museum Quest. Its tests begin green. Each lesson adds one kind of quality evidence without turning the project into a different application.
3. The learning sequence
| Step | Lesson | What you will leave behind |
|---|---|---|
| 1 | Write Python People Can Follow | A readable scorer and a justification for choices no formatter can make |
| 2 | Make Contracts Visible with Type Annotations | Honest input, result, collection, and structured-record annotations |
| 3 | Read MyPy Errors and Close Type Gaps | An annotated diagnostic log and a strict clean run |
| 4 | Format and Lint a Project with Ruff | A reviewed Ruff patch and committed project policy |
| 5 | Catch Small Problems Before Commit | A local hook gate with modified-file and failure evidence |
| 6 | Repeat the Quality Gate in CI | A CI workflow that repeats the local commands on a clean runner |
| 7 | Refactor in Small, Verified Steps | A sequence of small diffs protected by the complete gate |
| 8 | Unit Challenge | A maintainable Clockwork Observatory project and its quality record |
The order matters. Readability gives you a vocabulary for intent. Annotations make selected contracts checkable. Ruff and MyPy turn committed policy into repeatable feedback. Hooks shorten the feedback loop; CI checks committed state on a clean machine. Only then do you use the whole gate to support a larger refactor.
4. Prepare the local project
Unit 10 introduced environments, pyproject.toml, src/ layouts, and Git. Unit 13 introduced pytest and behavior baselines. Bring those skills forward; this unit applies them rather than reteaching every command.
Use this shape for the connected project:
From the project root, confirm the tools that are already declared by the course repository or install them in your own development environment:
The course examples are verified with Python 3.10-compatible syntax, Ruff 0.15.20, MyPy 2.2.0, pre-commit 4.6.0, and pytest 9.1.1. A later compatible version may format or phrase a diagnostic differently. When output differs, compare the rule code and behavior instead of forcing your terminal to match a screenshot character for character.
Before changing style or types, establish the behavior baseline:
A green baseline does not prove the program has no defects. It proves that the current tests pass in the current environment. Save the command, result, and commit state so a later failure can be attributed to a controlled change.
5. Use notebooks and the project for different jobs
The generated notebooks are useful for comparing two functions, inspecting annotations, tracing a type narrowing branch, and experimenting with small refactors. They are not a substitute for a Git working tree:
- pre-commit installs scripts under
.git/hooks/; - Ruff and MyPy discover committed project configuration from the filesystem;
- CI reads a workflow from
.github/workflows/; and - a clean runner is valuable precisely because it does not inherit notebook state.
Complete the project-shaped labs in a local directory. If a managed learning environment prevents GitHub access, you can still author the workflow and investigate the supplied CI transcripts; pushing a repository is not required to understand the gate.
6. Plan realistic stopping points
This unit involves more active setup and diagnosis than a syntax lesson. A reasonable rhythm is:
- Session 1: overview and readable Python;
- Session 2: type annotations;
- Session 3: MyPy diagnostics;
- Session 4: Ruff configuration and repair;
- Session 5: pre-commit hooks;
- Session 6: CI workflows;
- Sessions 7–8: refactoring lab and challenge.
Stop after a clean checkpoint rather than halfway through an unexplained tool failure. Keep an evidence log with these columns:
| Command or change | Expected result | Observed result | Explanation or next step |
|---|---|---|---|
pytest -q before edits |
Existing suite passes | 3 passed | Baseline is usable |
| One naming change | Same test result | … | … |
mypy src |
No type errors | … | … |
| Complete local gate | Every command exits zero | … | … |
7. Begin with the human reader
Automated tools are fast, but they cannot decide whether m means minutes, meters, a museum, or a mysterious temporary value. Start by learning which choices belong to the maintainer and how to justify them with the next likely change.