# 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: &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 }` ## 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?