03 · Deep modules, hidden information
When you finish this lesson you should be able to:
- Describe a module as interface plus implementation, not as “a file”
- Prefer depth: lots of behavior, few things to remember
- Catch information leaks before you draw more boxes
Core idea
Section titled “Core idea”A module is anything with an interface: a function, a class, a service, a whiteboard box. The interface is what callers must know. The implementation is everything else.
Deep modules hide a lot behind a little. Unix file I/O is the classic picture: open / read / write / close hide buffering, permissions, device drivers. Shallow modules have an interface almost as complex as the insides—a wrapper that adds a name and no power.
Shallow modules often come from classitis: a new type for every noun, each one a pass-through. More files, same cognitive load.
Information hiding is how you get depth. Put a decision in one place (retry policy, serialization, “what is an experiment assignment”) and do not let callers depend on it. Information leakage is the opposite: two modules both know the same awkward fact, so they must change together. Temporal decomposition is a common leak—“the HTTP handler class,” “the parse class,” “the respond class” sliced by when work happens instead of what secret they hide.
You can hide too much. If callers constantly need a knob that you buried, the interface is a lie. Depth is not secrecy for its own sake. It is putting the hard part behind a question the rest of the system can ask in one line.
Example
Section titled “Example”Feature flags.
Shallow: FlagStore, FlagParser, FlagCache, FlagEvaluator, FlagLogger—each a few lines, each leaking the shape of a flag record. Callers still assemble the pipeline.
Deeper: assignmentFor(user, flagKey) returns {on, variation}. Inside: fetch, cache, percentage rollout, overrides, “flag missing.” Callers do not know if the source is a JSON file or a service.
A leak to watch: if every screen passes user.createdAt, user.country, and user.hashedId because the evaluator is not allowed to look them up, the targeting rules have leaked into the UI. Either the evaluator is allowed to ask for a user context object, or targeting does not belong in the client.
flowchart LR app[App screens] flags[assignmentFor] store[Store cache rules] app -->|"user plus flag key"| flags flags --> store
The arrow into assignmentFor should stay small. If it grows a dozen fields, the interface is becoming as complex as the implementation.
Drill (12 minutes)
Section titled “Drill (12 minutes)”Paper. Boxes only.
- Design “experiment assignment for a user.”
- Draw at most four boxes.
- For each box, write the interface in one sentence (what it takes, what it returns).
- Circle any fact that appears in two boxes. That is a leak. Move it or name a new module that owns it.
- Delete any box whose interface is “calls the next box.” That was shallow.
Further reading
Section titled “Further reading”- A Philosophy of Software Design — Chapter 4, Modules Should Be Deep
- A Philosophy of Software Design — Chapter 5, Information Hiding (and Leakage)