# Design Principles ## 1. Simplicity First Keep solutions as simple as possible. Avoid over-engineering, premature abstractions, and unnecessary complexity. Three similar lines of code are better than a premature abstraction. - Don't add features, refactor code, or make "improvements" beyond what was asked. - Don't design for hypothetical future requirements. - Only add comments where the logic isn't self-evident. ## 2. Modularity and Separation of Concerns Structure code into well-defined modules with clear responsibilities. Each module should do one thing well. - Enforce clear boundaries between components. - Minimize coupling between modules; maximize cohesion within modules. - Prefer composition over inheritance. ## 3. Consistency Establish and follow consistent patterns across the codebase. Consistency reduces cognitive load and makes the code predictable. - Use uniform naming conventions, file organization, and coding style. - Follow established patterns unless there is a compelling reason to deviate. - Document deviations and the rationale behind them. ## 4. Fail Fast and Explicitly Surface errors early and clearly. Silent failures and implicit behavior lead to hard-to-debug issues. - Validate inputs at system boundaries (user input, external APIs). - Trust internal code and framework guarantees; don't over-validate. - Prefer explicit error handling over silent fallbacks. ## 5. Security by Default Never introduce security vulnerabilities. Prioritize safe, secure, and correct code. - Guard against OWASP top 10 vulnerabilities (injection, XSS, etc.). - Never trust external input without validation. - If insecure code is written inadvertently, fix it immediately. ## 6. Reversibility and Low Blast Radius Prefer reversible, low-impact changes. Destructive or hard-to-reverse actions require extra caution and confirmation. - Default to local, reversible operations (edits, tests). - For destructive or shared-state actions, confirm before proceeding. - Measure twice, cut once. ## 7. Readability Over Cleverness Write code that is easy to read and understand. Clever or obscure code is a liability. - Use clear, descriptive names for variables, functions, and modules. - Avoid unnecessary indirection or meta-programming. - Make the intent obvious; hide only what truly doesn't matter. ## 8. Test What Matters Focus testing effort on behavior that matters. Don't test framework guarantees or internal details that can't break. - Integration tests for system boundaries and critical paths. - Unit tests for complex logic and edge cases. - Don't mock what you don't own; prefer real dependencies in integration tests. ## 9. Evolve Incrementally Make small, incremental changes rather than large rewrites. Each change should be understandable and reversible on its own. - Prefer incremental improvements over big-bang refactors. - Each commit should represent a coherent, reviewable change. - Avoid branching work that diverges for long periods. ## 10. Document Decisions, Not Mechanics Code explains how; commit messages explain why. Don't duplicate what the code already says. - Record non-obvious decisions and their rationale. - Don't document what can be derived from reading the code. - Keep documentation close to the code it describes.