| name | design-philosophy |
| description | Code and module-level design judgment — complexity management, interface design, naming, abstraction depth, error handling philosophy, information hiding. Use when: writing new modules, reviewing code quality, refactoring decisions, API design, naming discussions. Triggers: design judgment, code quality, module design, complexity, naming, abstraction, error handling, code smell |
Design Philosophy
Design judgment framework for code and module-level decisions. Not architecture (→ architect-thinking), not testing (→ testing-strategy), not refactoring mechanics (→ refactoring-judgment).
Complexity — The Root Enemy
Three forms (Ousterhout):
| Form | Signal | Mitigation |
|---|
| Change Amplification | Simple change requires edits in many places | Consolidate knowledge behind single interface |
| Cognitive Load | Reader must hold too much context to understand | Reduce state, narrow interfaces, isolate concerns |
| Unknown Unknowns | Not obvious what to change or what breaks | Make dependencies explicit, document non-obvious behavior |
Strategic programming: invest 10-20% extra to reduce future complexity. Tactical programming (just make it work) compounds tech debt.
Deep Modules vs Shallow Modules
- Deep module: simple interface, complex implementation → maximizes information hiding
- Shallow module: interface nearly as complex as implementation → adds ceremony, not value
- Prefer fewer, deeper modules over many thin wrappers
- Classitis (too many tiny classes/functions) increases cognitive load through indirection
Information Hiding & Leakage
- Each module should encapsulate design decisions that are likely to change
- Information leakage: when the same knowledge appears in multiple modules — even across interface boundaries
- Temporal decomposition (structuring by execution order) often causes leakage — group by knowledge instead
- If two modules always change together, they share a hidden design decision → merge or re-abstract
Define Errors Out of Existence
- Best error handling is no error handling — design APIs so invalid states are unrepresentable
- Widen input tolerance: accept broader inputs, normalize internally
- Mask exceptions internally when possible (e.g.,
delete succeeds even if item absent)
- Reserve exceptions for conditions the caller truly needs to handle
- Dead Programs Tell No Tales: crash early on programmer errors rather than limping through corrupted state
DRY — Knowledge, Not Text
- DRY targets duplication of knowledge (business rules, decisions), not textual similarity
- Two identical code blocks serving different business reasons are NOT duplication
- Premature DRY creates coupling between unrelated concepts → worse than repetition
- Test: "If requirement A changes, must I also change B?" If no → not duplication
Orthogonality
- Changes in one module should not force changes in unrelated modules
- Favor composition over inheritance (GoF) — inheritance creates tight coupling
- Encapsulate what varies (GoF) — isolate likely-to-change decisions behind stable interfaces
- Program to an interface, not an implementation (GoF)
Tracer Bullets
- Build a thin end-to-end slice through all layers first to validate assumptions
- Differs from prototyping: tracer bullet code is production code, just minimal
- Reveals integration issues early, provides a skeleton for incremental build-out
Design by Contract
- Preconditions: what must be true before calling
- Postconditions: what is guaranteed after return
- Invariants: what remains true throughout lifetime
- Assertive Programming: use assertions for invariants and preconditions; fail loud and fast
Functions & Naming
| Principle | Guideline |
|---|
| Single Responsibility | One function = one reason to change |
| Intention-Revealing Names | Name describes what, not how — avoid encodings (Hungarian notation) |
| Command-Query Separation | Commands mutate state, queries return data — never both |
| Boy Scout Rule | Leave code cleaner than you found it — small, opportunistic improvements |
| Function Length | If scrolling is needed, function is likely too long. But don't split just to be short — split at abstraction boundaries |
Comments as Design Tool
- Good comments describe what is not obvious from the code — design rationale, non-obvious constraints, edge cases
- Interface comments: describe what, preconditions, postconditions, side effects
- Bad comments: restate what the code does, or apologize for complexity instead of simplifying
- If a comment explains why code is non-obvious, keep it. If it explains what code does, refactor the code instead
Code Smells — Design Quality Signals
Smells indicate design problems. Identify; refactoring mechanics belong to refactoring-judgment.
| Smell | Signal |
|---|
| Long Method | Function does too many things; hard to name precisely |
| Feature Envy | Method accesses another object's data more than its own |
| Data Clumps | Same group of parameters/fields appears repeatedly |
| Primitive Obsession | Domain concepts represented as raw strings/ints |
| Shotgun Surgery | One change requires edits across many classes |
| Divergent Change | One class modified for multiple unrelated reasons |
| Message Chains | a.b().c().d() — tightly coupled to navigation structure |
Decision Checklist
Before committing a design, verify:
| Check | Question |
|---|
| Depth | Is the module deep — simple interface, rich implementation? |
| Hiding | Does each module hide a design decision likely to change? |
| Leakage | Is the same knowledge duplicated across module boundaries? |
| Error surface | Can the API be redesigned to eliminate error cases? |
| Orthogonality | Can this module change without rippling into others? |
| Naming | Do names reveal intent without requiring context? |
| DRY correctness | Is shared code truly shared knowledge, not coincidental similarity? |
| Smell-free | No long methods, feature envy, data clumps, shotgun surgery? |
Reference Books
| Book | Author(s) | Key Contribution |
|---|
| A Philosophy of Software Design | John Ousterhout | Complexity theory, deep modules, information hiding |
| The Pragmatic Programmer | David Thomas, Andrew Hunt | DRY, orthogonality, tracer bullets, design by contract |
| Clean Code | Robert C. Martin | Naming, small functions, CQS, Boy Scout Rule |
| Refactoring | Martin Fowler | Code smells as design quality signals |
| Design Patterns | Gamma, Helm, Johnson, Vlissides | Program to interfaces, composition over inheritance, encapsulate variation |