70 lines
3.9 KiB
Markdown
70 lines
3.9 KiB
Markdown
# SOLID & DRY Design Principles
|
|||
|
|
|
||
|
|
## Single Responsibility Principle (SRP)
|
||
|
|
- A class/module should have one and only one reason to change.
|
||
|
|
- Each module owns exactly one actor's requirements.
|
||
|
|
- Bad: `UserService` that handles auth, email, and database. Good: separate `AuthService`, `EmailService`, `UserRepository`.
|
||
|
|
- In Rust: one struct per concern. `ChunkProcessor` doesn't also handle HTTP routing.
|
||
|
|
|
||
|
|
## Open/Closed Principle (OCP)
|
||
|
|
- Software entities should be open for extension, closed for modification.
|
||
|
|
- Use traits/interfaces to allow new behavior without changing existing code.
|
||
|
|
- Strategy pattern: `trait Scorer { fn score(&self, doc: &Doc) -> f64; }` — add new scorers without modifying search.
|
||
|
|
- In Rust: trait objects or generics. `fn process<S: Strategy>(s: &S)` — new strategies don't touch `process`.
|
||
|
|
|
||
|
|
## Liskov Substitution Principle (LSP)
|
||
|
|
- Subtypes must be substitutable for their base types without breaking correctness.
|
||
|
|
- If `fn accept(animal: &dyn Animal)` works with `Dog`, it must work with `Cat` too.
|
||
|
|
- Violated when: subtype throws unexpected errors, ignores base contract, strengthens preconditions.
|
||
|
|
- In Rust: trait implementations must honor the trait's documented contract.
|
||
|
|
|
||
|
|
## Interface Segregation Principle (ISP)
|
||
|
|
- Clients should not be forced to depend on interfaces they don't use.
|
||
|
|
- Many small traits > one fat trait.
|
||
|
|
- Bad: `trait Repository { fn read(); fn write(); fn delete(); fn audit(); }` — read-only clients forced to see write methods.
|
||
|
|
- Good: `trait Readable`, `trait Writable`, `trait Auditable` — compose as needed.
|
||
|
|
- In Rust: supertraits for composition: `trait FullRepo: Readable + Writable + Auditable {}`
|
||
|
|
|
||
|
|
## Dependency Inversion Principle (DIP)
|
||
|
|
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
|
||
|
|
- In Rust: accept `impl Trait` or `&dyn Trait`, not concrete types.
|
||
|
|
- `fn search(store: &dyn VectorStore)` — works with Postgres, OpenSearch, or in-memory mock.
|
||
|
|
- Constructor injection: `struct SearchEngine { store: Box<dyn VectorStore> }`
|
||
|
|
|
||
|
|
## DRY (Don't Repeat Yourself)
|
||
|
|
- Every piece of knowledge should have a single, unambiguous, authoritative representation.
|
||
|
|
- DRY is about knowledge, not code. Two functions with same code but different reasons to change are NOT duplication.
|
||
|
|
- Extract when: same logic appears 3+ times AND changes for the same reason.
|
||
|
|
- Wrong DRY: coupling unrelated code just because it looks similar. Right DRY: shared business rules in one place.
|
||
|
|
|
||
|
|
## WET (Write Everything Twice) — When DRY Goes Wrong
|
||
|
|
- Premature DRY creates coupling worse than duplication.
|
||
|
|
- Rule of three: duplicate is fine, triplicate means extract.
|
||
|
|
- Tests should be WET — readability > DRYness in test code.
|
||
|
|
- Configuration can be WET — explicit is better than magic shared config.
|
||
|
|
|
||
|
|
## KISS (Keep It Simple, Stupid)
|
||
|
|
- Simplest solution that works is usually the best.
|
||
|
|
- Avoid: premature abstraction, speculative generality, framework-itis.
|
||
|
|
- Measure complexity: if a new team member can't understand it in 15 minutes, simplify.
|
||
|
|
|
||
|
|
## YAGNI (You Aren't Gonna Need It)
|
||
|
|
- Don't build features until you actually need them.
|
||
|
|
- Speculative code rots — it's untested, unmaintained, and misleading.
|
||
|
|
- Exception: known architectural boundaries (API versioning, database migrations).
|
||
|
|
|
||
|
|
## Composition Over Inheritance
|
||
|
|
- Prefer composing objects over class hierarchies.
|
||
|
|
- In Rust: no inheritance. Composition is the default via struct fields + trait delegation.
|
||
|
|
- `struct HttpServer { router: Router, auth: AuthMiddleware, rate_limiter: RateLimiter }`
|
||
|
|
|
||
|
|
## Law of Demeter
|
||
|
|
- Only talk to your immediate friends. Don't chain: `a.b().c().d()`.
|
||
|
|
- Tell, don't ask: `order.ship()` not `order.get_warehouse().get_shipping().create_label()`.
|
||
|
|
- In Rust: expose methods that encapsulate internal structure.
|
||
|
|
|
||
|
|
## Practical Application
|
||
|
|
- Start concrete, extract abstractions when patterns emerge.
|
||
|
|
- Refactor in small steps with tests as safety net.
|
||
|
|
- Code review checklist: SRP violated? Unnecessary coupling? Duplicated knowledge? Over-engineered?
|