module-depth
Skill0 starsChanged 3 months agoMITView on GitHub
This skill should be used when the user asks "should I split this class", "is this abstraction worth it", "why does this change ripple through so many layers", "is this wrapper useful", or when reviewing a new interface, wrapper, or layer. Applies Ousterhout's deep-versus-shallow module test, including when Clean Code's small-functions advice does not apply. Covers whether an abstraction earns its interface. For where validation belongs use `parse-dont-validate`.
In this repository29 other files it gives its agents.
- algorithmic-complexity-attacksSkill
- cache-stampedeSkill
- clocks-and-timeSkill
- code-hotspotsSkill
- connascenceSkill
- connection-poolingSkill
- consistent-hashingSkill
- coordinated-omissionSkill
- cyclomatic-complexitySkill
- database-queuesSkill
- dependency-confusionSkill
- expand-contractSkill
- floating-pointSkill
- hyrums-lawSkill
- index-designSkill
- isolation-anomaliesSkill
- keyset-paginationSkill
- metamorphic-testingSkill
- mutation-testingSkill
- mvcc-and-bloatSkill
- parse-dont-validateSkill
- query-plansSkill
- queueing-and-utilizationSkill
- retry-amplificationSkill
- start-hereSkill
- tail-latencySkill
- unicode-pitfallsSkill
- universal-scalability-lawSkill
- write-amplificationSkill
The file
We split this skill into the rules it gives an agent
9 rules. About 0 are common, 7 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈7
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 8
Every module has two sides, and its worth is the gap between them.
- Distinctive: line 27
Do not split when the pieces are only usable together, since that adds an interface and hides nothing.
- Distinctive: line 29
Test after any proposed split: can either half be understood and used without reading the other?
- Very specific: line 53
When both FileReader and FileWriter know the header layout, a format change touches both.
- Distinctive: line 76
Write the interface out in full: signatures, errors, ordering rules, config, side effects.
- Distinctive: line 79
Check whether any design decision is now written in two places.
- Distinctive: line 80
Ask explicitly whether this could be one module instead of three.
- Very specific: line 81
Check the name. Utils, Manager, Helper, and Service usually mark a module with no coherent secret to hide.
- Distinctive: line 85
Prefer interfaces slightly more general than today's need, with special cases layered above.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Module depth
Every module has two sides, and its worth is the gap between them.
- Interface: everything a caller must know. Not just signatures, but exceptions, required ordering, side effects, config read, and errors to handle.
- Implementation: everything it does.
A module's value is functionality hidden minus interface cost.
A deep module has a small interface and substantial functionality behind it. A shallow module has an interface nearly as complex as its implementation, so it costs more to learn than it saves.
The framing is John Ousterhout's, from A Philosophy of Software Design. Unix file I/O is the canonical deep module: open, read, write, close, lseek hide disk scheduling, buffering, permissions, block allocation, device drivers, and the difference between a file, a pipe, and a socket.
Why this outranks "keep it small"
Advice to write small classes and small functions optimizes only the implementation side. Applied without the cost side it produces classitis: many tiny classes, each simple, with the complexity relocated into the relationships between them and thirty interfaces to learn instead of three.
Reconcile the two rules this way:
- Split when the pieces have genuinely separate interfaces and a caller needs only one of them.
- Do not split when the pieces are only usable together, since that adds an interface and hides nothing.
Test after any proposed split: can either half be understood and used without reading the other? If not, the split made things worse.
Red flags
Pass-through methods
A method whose body calls a method with the same signature one layer down.
public void charge(Card c, Money m) {
processor.charge(c, m); // adds interface, hides nothing
}Fix by exposing the lower layer directly, merging the layers, or making the wrapper do real work such as retries, validation, or translation into domain terms. One thin adapter at a boundary owned for testing or vendor-swap reasons is a real if small benefit. Three stacked adapters are not.
Pass-through variables
A parameter threaded through five functions because only the fifth uses it. Every function in the chain now carries that concept in its interface. Fix with a context object, dependency injection, or module-level state, each of which has costs, so choose deliberately.
Information leakage
The same design decision reflected in more than one module. This is the deepest form of coupling and the one worth hunting.
When both FileReader and FileWriter know the header layout, a format change touches both. Fix by making one module own the format, with reading and writing inside it.
Leakage usually comes from temporal decomposition: splitting modules by when things happen (read, then process, then write) instead of by what knowledge they hold. Order of operations is the wrong axis, because the same knowledge is needed at several times.
Interfaces that mirror implementation
An interface with one method per internal step is a to-do list, not an abstraction. Callers must sequence the steps correctly, which adds connascence of execution order on top of a shallow interface. See connascence.
Define errors out of existence
The most reliable way to deepen a module is to reduce the number of ways it can fail.
| Design that throws | Design that does not |
substring() throws on out-of-range indices | Python slicing clamps: xs[2:99] returns what exists |
delete fails if the file is open (Windows) | Unix unlinks the name, the file goes when the last handle closes |
unset errors when the variable is absent | Define "remove it" as "ensure it is gone" |
| Map lookup throws on a missing key | Return a default or an explicit optional |
Do not define away errors that matter: a failed payment must surface. Target incidental errors, where the caller's only sane response is to ignore it or do the obvious thing. Each one removed deletes a branch from every call site, lowering caller complexity too. See cyclomatic-complexity.
Review checklist
- Write the interface out in full: signatures, errors, ordering rules, config, side effects. A long list means a shallow module regardless of how clean the code is.
- Name what a caller can now stop knowing. "Nothing, it is just organized differently" means this is not an abstraction.
- Confirm each layer changes the level of abstraction. A layer speaking the same vocabulary as the one below is a pass-through in disguise.
- Check whether any design decision is now written in two places.
- Ask explicitly whether this could be one module instead of three. Review bias runs the other way.
- Check the name.
Utils,Manager,Helper, andServiceusually mark a module with no coherent secret to hide.
Somewhat general-purpose
Prefer interfaces slightly more general than today's need, with special cases layered above.
A text editor exposing insert(text, pos) and delete(start, end) is both simpler and more capable than one exposing backspace(), deleteSelection(), and deleteWord(). The general interface is smaller, and each special case becomes a short function on top.
This is not speculative generality. No unused features are added. A smaller, more fundamental interface is chosen for functionality already required.
When to apply it
At design and code review, before the interface ships. Retrofitting depth means changing every call site, which is exactly the cost the metric predicts.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

