Delete It. Did Things Get Simpler?
Delete the module. Did things get simpler?
We're trained to add abstraction, so we keep adding it — a wrapper here, a helper there, a service in front of a service. But not every module earns its keep. A module is supposed to hide complexity. If its interface is nearly as complicated as the code behind it, it hides nothing. It's just an extra hop you have to read through to understand what actually happens.
Deep vs shallow modules
John Ousterhout's A Philosophy of Software Design frames this precisely. The value of a module is its interface complexity subtracted from its functionality.
- A deep module presents a small, simple interface over a lot of behavior.
read()andwrite()in a filesystem hide enormous machinery behind two calls. High value. - A shallow module presents an interface nearly as complex as its implementation. It offers little functionality per unit of interface. Low, sometimes negative, value.
A shallow module is negative-value because the interface itself is a cost: every caller must learn it, and it doesn't buy them any hidden complexity in return.
# Shallow passthrough: the interface is as wide as the body.
def get_user_name(user_service, user_id):
return user_service.get_user(user_id).name
# Callers already have user_service. This hides nothing;
# it just adds a name to memorize and a file to open.
Run the module test
The concrete check from the reel: delete the module and inline its code at every call site. Did the whole system get simpler?
If inlining makes things clearer — fewer files, fewer names, a shorter path from question to answer — the module was a shallow passthrough. Fold it in. If inlining makes things worse — the same non-trivial logic copy-pasted everywhere, a genuine abstraction lost — then the module was pulling its weight. Keep it.
| After inlining... | Verdict |
|---|---|
| System is simpler, less to read | Shallow passthrough — delete it |
| Logic duplicated, real abstraction lost | Deep module — keep it |
| Roughly a wash | Lean toward deleting; fewer parts win |
Why passthroughs hurt
Shallow modules feel like architecture but act like friction. Each one splinters the design into more pieces, so understanding any single behavior means hopping through more layers. "Classitis" — the belief that more, smaller classes and modules are always better — produces exactly this: a system of thin wrappers where the interfaces cost more than the implementations hide. Prefer fewer, deeper modules that each hide something real.
Power moves
- Count the hops. If tracing one operation opens five files that each forward to the next, most of those layers are shallow.
- Interface-to-implementation ratio. When the signature and docstring are as long as the body, that's a passthrough smell.
- Don't confuse a boundary with a wrapper. A thin adapter at a true system boundary (an anti-corruption layer, a port) can be worth it; a thin wrapper in the middle of your own code usually isn't.
- Resist premature extraction. Wait until logic is actually reused or genuinely complex before giving it its own module.
Resources
- A Philosophy of Software Design — John Ousterhout
- John Ousterhout on deep vs shallow modules (talk)
- The Wrong Abstraction — Sandi Metz
- "Rule of Three" — Martin Fowler, Refactoring
Shipping AI or cloud systems? Yeda AI audits and hardens production LLM and application codebases. Talk to us · Read the blog