| name | module-design |
|---|---|
| description | Use when creating new modules, splitting responsibilities, designing APIs, or deciding what to expose publicly — guides deep module design and information hiding |
Based on John Ousterhout's "A Philosophy of Software Design."
Divide each module into interface (what callers must know) and implementation (everything else). The interface should be much simpler than the implementation.
- Deep: small interface, lots of functionality, lots hidden
- Shallow: complex interface and/or little functionality — not worth the cost of learning it
- Every module adds complexity through its interface — get maximum functionality for that cost
- Size doesn't matter much (200-2000 lines is fine). Depth matters.
The single most important idea in software design.
- Each module encapsulates knowledge/decisions known only to it
- The interface should not reflect internal details
- Information leakage (the opposite): implementation details exposed, other modules depend on them
- Temporal decomposition: structuring code around execution order instead of information hiding — a common cause of leakage
- When you see leakage, bring all related information together in one place
Ask yourself:
- What unique value does this module provide?
- What key knowledge does it use to provide that value?
- What's the least of that knowledge that must be exposed?
- Capabilities reflect current needs, but design interfaces generic enough for reuse
- Results in simpler, deeper interfaces than special-purpose designs
Ask yourself:
- What is the simplest API that covers all my current needs?
- In how many situations will this function be used?
- Is this API convenient for my current needs?
- Each layer's abstraction should differ from layers above and below
- Red flag: pass-through functions that add no value
- Design interfaces around what's done most frequently
- Look for common features across related tasks; design APIs for those
- Module authors should take on hard problems and solve them completely
- Pull complexity down into modules — simple APIs over simple implementations
- Handle error conditions rather than raising/throwing
- Minimize configuration parameters — if you don't know the right value, neither will your users
- Shallow modules (interface ~= implementation complexity)
- Pass-through functions
- Information leakage between modules
- Temporal decomposition driving module boundaries
- Exposing internal details through public APIs