Module 056 min

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.

After this module
  • 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:

Cohesion
Do the things in this module belong together? A module that calculates prices, formats invoices and sends email has three reasons to change. A module that only knows pricing rules has one.
Coupling
How much does a change here force a change there? Fewer, narrower connections mean changes stay local. The real test is not "does it look decoupled" but "what breaks when this changes?"

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.

Try it · change impactclick a module

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

IdeaWhat it actually meansIts failure mode
Names reveal intentcalculateMonthlyPayment over calc(a, b)Renaming to be "clever" instead of clear
Functions do one thingOne reason to change; a short body you can read without scrollingSplitting so finely that following a flow takes ten files
DRYOne authoritative place for each piece of knowledgeSharing code that merely looks alike but changes for different reasons
KISS / YAGNISimplest thing that works; build for today's requirementUsing "simplicity" to avoid necessary structure
Composition over inheritanceAssemble behavior; inherit only for genuine is-aDeep hierarchies where every change affects descendants
SOLIDA lens for spotting fragility — especially Liskov, the one that causes real bugsManufacturing 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))
The trap · pattern worship

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.

Check yourselfQuestion 1 of 1

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.