Design in the small
Architecture gets the attention; most of your daily pain comes from how functions, names and modules are arranged at arm's length.
- You can judge a piece of code by two properties — cohesion and coupling — instead of by taste.
- You know when to extract an abstraction and, more importantly, when not to.
Design at the small scale is not about patterns. It is about two properties that you can evaluate in a code review without a philosophy degree:
Everything else — small functions, clear names, dependency injection, interfaces — is a tactic in service of those two. Which is why "this looks clean" is not a review comment, and "changing this touches 14 files" is.
5.1Feel the blast radius
Pick a module to change. The highlighted ones depend on it, directly or through others — that is the real size of your change, regardless of how small the edit looks.
A database is the classic example: changing its schema touches nearly everything, which is why schema changes deserve the slowest, most reviewed process you have.
5.2The rules that survive contact
| Idea | What it actually means | Its failure mode |
|---|---|---|
| Names reveal intent | calculateMonthlyPayment over calc(a, b) | Renaming to be "clever" instead of clear |
| Functions do one thing | One reason to change; a short body you can read without scrolling | Splitting so finely that following a flow takes ten files |
| DRY | One authoritative place for each piece of knowledge | Sharing code that merely looks alike but changes for different reasons |
| KISS / YAGNI | Simplest thing that works; build for today's requirement | Using "simplicity" to avoid necessary structure |
| Composition over inheritance | Assemble behavior; inherit only for genuine is-a | Deep hierarchies where every change affects descendants |
| SOLID | A lens for spotting fragility — especially Liskov, the one that causes real bugs | Manufacturing interfaces and layers for one implementation |
Two of these deserve a longer sentence. DRY is about knowledge, not text: two functions that look identical but exist for different business reasons should stay separate — merging them couples two things that will drift apart. And the Rule of Three is the practical version: extract the abstraction the third time you see the pattern, not the first. By then you know what actually varies.
# before: one function, four jobs, testable only through the HTTP layer
def handle_checkout(req):
validate(req); totals = sum_cart(req); save_order(totals); send_email(req)
# after: the rule is isolated and directly testable
def order_total(items, discount=None): ... # pure: in → out, no I/O
def create_order(user_id, items): ... # orchestration: calls the above
def handle_checkout(req): return create_order(req.user_id, parse(req))
Six layers of indirection for a CRUD screen, an interface with exactly one implementation, a plugin system nobody asked for. Each was added in the name of good design and each costs a reader three jumps to understand anything. Indirection is a loan against future change: take it when the change is likely, not because the book said so.
Two functions look almost identical but one computes a tax rule and the other a shipping discount. Should you merge them?
60-second recap
- Judge design by cohesion and coupling, not by how modern it looks.
- Blast radius is the real measure of a change — check it before you promise it's quick.
- DRY means one place for each piece of knowledge, not one function for every similar line.
- Abstraction is a loan. Take it when change is likely, not on spec.