Lesson 0006 · Core Designer · Module 2
Design Around Reasons to Change
Responsibilities are fuzzy; change sources are facts. Structure your modules around the forces that will actually edit them.
Mission tie-in: "design around reasons to change" is the upgrade from lesson 0002's SRP. It turns a principle into a method you can apply before writing a line.
Knowledge: the change source is the unit
Every future edit to your codebase comes from a finite list of forces: tax law changes, a new payment provider, a new report format, a re-branding. Call each one a reason to change. Design says: code that changes for the same reason lives together; code that changes for different reasons lives apart.
Consider an invoice class:
class Invoice:
def __init__(self, lines: list[Line], vat_rate: Decimal) -> None:
self.lines = lines
self.vat_rate = vat_rate
def total(self) -> Money:
subtotal = sum(line.price for line in self.lines)
return Money(subtotal * (1 + self.vat_rate))
def to_html(self) -> str:
rows = "".join(f"<tr><td>{l.name}</td></tr>" for l in self.lines)
return f"<table>{rows}</table>"
def to_json(self) -> dict:
return {"lines": [{"name": l.name, "price": str(l.price)} for l in self.lines]}
Two reasons to change are welded together: how invoices are calculated (tax rules, discounts) and how invoices are rendered (HTML tweaks, new JSON fields for a mobile client). When the mobile client needs a new field, you edit the class that owns the tax calculation — and risk the tax rules.
The stable design separates them:
class Invoice:
def __init__(self, lines: list[Line], vat_rate: Decimal) -> None:
self.lines = lines
self.vat_rate = vat_rate
def total(self) -> Money: # reason: pricing rules
subtotal = sum(line.price for line in self.lines)
return Money(subtotal * (1 + self.vat_rate))
class InvoiceRenderer:
def __init__(self, invoice: Invoice) -> None: ...
def to_html(self) -> str: ... # reason: web presentation
def to_json(self) -> dict: ... # reason: API contract
Now a pricing change edits Invoice and nothing else; a rendering change edits InvoiceRenderer and nothing else. Note what you did not do: you didn't count methods, and you didn't split for symmetry. You named the change sources — the whole method.
| You see this | What it costs | The move |
|---|---|---|
| git log --format=%s -- billing.py | The commit subjects name this file’s change sources out loud. | Group the subjects; each distinct world is a module. |
| Invoice.to_html() beside Invoice.total() | A branding tweak edits the file that owns the tax rules. | Move rendering out; the calculation stops being touched. |
| one class edited by three teams | Three worlds, one file — every change becomes a negotiation. | Split along the team boundary (lesson 0033). |
| a helpers.py nobody claims | Its change source was never named, so nobody owns its edits. | Ask who edits it and why; that answer is its home. |
Skill: name the change sources
The invoice class above should be split because:
A class with many methods and one change source is:
To discover a module's reasons to change, ask:
Practice on your own code
Pick your most-edited class. List the world events that have actually forced edits to it in the last year (a law, a vendor, a format, a team). If the list names two or more distinct worlds, you have found a split that would have paid for itself.
Reveal: a worked split
A ReportGenerator that both queries the warehouse and formats PDFs. Edit history: new warehouse schema (twice) and new branding (once). Two worlds. Split into WarehouseReportQuery (owned by the warehouse team's contract) and ReportFormatter (owned by design). Branding changes stop touching query code.
Your win
You can now enumerate a module's reasons to change from its edit history — and split along those lines instead of along method counts or symmetry.
Read and watch deeper
- Clean Architecture, Martin — ch. 7 (SRP as "one reason to change") and ch. 8 (Open-Closed: design for extension along the change source).
- A Philosophy of Software Design, Ousterhout — ch. 2 "The Nature of Complexity": complexity defined as "anything that makes change hard".
- Refactoring, Fowler — ch. 3 "Bad Smells": smells are symptoms; change sources are the diagnosis.
- Watch: ArjanCodes YouTube — search "single responsibility".
- Reference: Design Principles — SRP; and the glossary on reason to change.
Bring a class you edit often to your agent-teacher and co-list its change sources — disagreement is where the insight lives.