# Software Design Principles

Fundamental principles for arranging code so it can be understood, changed, and extended over time. These are compressed experience — tools with a domain of applicability, not laws.

## Core Properties: Coupling & Cohesion

**Manage coupling and cohesion first** — most named principles are indirect ways of improving these two properties.

- **Coupling**: Degree of interdependence between modules. Low coupling = changes don't ripple.
- **Cohesion**: Degree to which elements within a module belong together. High cohesion = module does one thing well.

When reviewing a design, ask: *Which changes ripple across module boundaries?* Those ripples are the true measure of coupling.

---

## The Major Acronyms

### SOLID — Use as a Lens, Not a Checklist

| Principle | What It Means | When to Apply |
|---|---|---|
| **S**ingle Responsibility | A module has one reason to change (one kind of change) | Keep modules cohesive; apply to classes, functions, services |
| **O**pen/Closed | Open to extension, closed to modification | Where extension points are *real* (not speculative) |
| **L**iskov Substitution | Subtypes must be substitutable without breaking contracts | The only verifiable one; violations are real bugs |
| **I**nterface Segregation | Many specific interfaces > one general interface | When clients genuinely need different subsets |
| **D**ependency Inversion | Depend on abstractions, not concretions | At genuine architectural boundaries |

**Anti-pattern**: Manufacturing interfaces, factories, and layers just to satisfy the acronym when there's only one implementation and no second one in sight. Indirection has a cost — you pay it on every read.

---

### DRY — Apply to Knowledge, Not Text

> **Don't Repeat Yourself**: Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.

**Key distinction**: DRY is about *knowledge*, not code that looks alike.
- Two pieces of code that look similar but change for different reasons should stay separate
- Prefer a little duplication over a premature shared abstraction that couples unrelated things
- **Rule of Three**: Extract the abstraction once the real pattern has shown up 2-3 times

**WET** (Write Everything Twice) = DRY violation. But forced DRY = wrong abstraction.

---

### KISS — Keep It Simple, Stupid

Among solutions that do the job, pick the simplest. Complexity is a cost you pay forever.

**Over-engineering signals**:
- Using microservices, Hexagonal Architecture, or Clean Architecture for simple CRUD
- Excessive design pattern application
- Configurable frameworks, plugin systems, extension points nobody asked for

**Four Rules of Simple Design** (priority order):
1. Passes the tests
2. Reveals intent
3. No duplication
4. Fewest elements

---

### YAGNI — You Aren't Gonna Need It

> Implement a feature only when you have strong evidence it will be used.

**Not**: "Never build flexibility" — some flexibility (stable interfaces, clean seams) is cheaper to build in early.

**Balance**: YAGNI argues against *speculative implementation*, not against *thoughtful boundaries*.

---

## Additional Principles

### CQS — Command Query Separation (Bertrand Meyer)

Methods should be either:
- **Commands**: Return void, have side effects (modify state)
- **Queries**: Return values, no side effects

*Asking a question should not change the answer.* Queries are inherently safer — easier to understand, call, and test.

---

### Law of Demeter — Talk Only to Your Immediate Friends

Don't reach through `a.b.c.d` chains. Reduces coupling by limiting knowledge of internal structure.

---

### Composition Over Inheritance

Assemble behavior by composition by default; reserve inheritance for genuine "is-a" relationships. Inheritance breaks encapsulation of the parent.

---

### Tell, Don't Ask

Give the behavior to the owner of the data instead of pulling data out to manipulate elsewhere. Encapsulates behavior with its data.

---

### Design by Contract

State preconditions, postconditions, and invariants explicitly. Say what "correct" means.

---

### Fail Fast

Surface an error as early and as close to its cause as possible. Don't let a bad state spread far before it's noticed.

---

### Validate at the Boundary, Trust the Inside

Clean untrusted data once at the entrance so core logic doesn't guard everywhere.

---

### Make Illegal States Unrepresentable

Use types to rule out impossible states at compile time instead of checking at runtime.

---

### Least Privilege

Each module gets only the minimum access and information it needs to do its job.

---

### Single Level of Abstraction

Within one function body, don't mix high-level intent with low-level detail. It should read at one altitude.

---

### Encapsulation / Information Hiding

Expose *what*, hide *how*. The implementation may change; the contract may not.

---

### Principle of Least Astonishment

An interface should behave the way its user expects. Surprise is where defects breed.

---

### Orthogonality

Changing one thing doesn't ripple into an unrelated thing. Modules don't reach into each other's internals.

---

### Reversibility

Don't weld decisions in place. Keep critical choices (database, framework, vendor) easy to replace.

---

### Boy Scout Rule

Leave code a little cleaner than you found it. Small steps — small changes, frequent verification.

---

### Good Enough

Stop at "good enough" and spend saved energy where it matters more. Don't polish endlessly.

---

## Principle Conflicts — Where Judgment Lives

| Conflict | How to Settle |
|---|---|
| **DRY vs Decoupling** | Removing duplication sometimes creates coupling. Test: will they change for the same reason? If yes, merge; if they just resemble each other today, leave them apart. |
| **Abstraction vs YAGNI** | Premature abstraction is speculative complexity. Wait for the third repetition (Rule of Three) before extracting; tolerate the first two. |
| **Flexibility vs Simplicity** | Open an extension point only along the axis where change will actually come; keep other dimensions closed and simple. Extensible everywhere = over-engineering. |
| **Performance vs Readability** | Be correct and readable first; once a profiler finds the bottleneck, trade readability for speed *locally and with a comment*. |
| **Consistency vs Improvement** | In a codebase with poor conventions, "consistent" and "better" collide. Improve in place at small scope; at large scope, unify the convention first — don't add a third style. |

**Tie-breaker**: Correct > Clear > Simple > everything else.

---

## Key Takeaways

1. **Principles are tools, not laws** — each has a failure mode
2. **Coupling and cohesion are the deeper properties** — acronyms serve them
3. **Optimize for the changes you'll actually make** — not imaginary ones
4. **Duplication is cheaper than the wrong abstraction** — wait for the pattern to clear
5. **Make dependencies explicit and point them toward stable things**
6. **Model the domain in the language of the domain** — align software boundaries with business boundaries
7. **Choose paradigms to fit the problem, not ideology** — most large systems are pragmatically mixed

---

## Related Notes

- [[Architecture/Architectural-Styles]] — How principles map to architectural styles
- [[Patterns/Design-Patterns]] — Patterns as vocabulary for recurring solutions
- [[Methodologies/Extreme-Programming]] — XP practices that embody these principles
- [[Testing/Test-Driven-Development]] — TDD as a design practice

---

*Sources: Software Engineering Guide (software-engineering-guide.github.io), Steven's Knowledge (stevenzg.com), Software Engineering: A Modern Approach (softengbook.org), Tenhaw Engineering Handbook, O'Reilly Clean Architecture*