Design Principles
Good software is not just software that works - it is software that is easy to understand, change, and extend over time. Design principles are guidelines that help developers write code that is maintainable, flexible, and robust.
These principles are language-agnostic. Whether you write Python, Java, Go, or TypeScript, the underlying concepts apply equally.
SOLID Principles
Section titled “SOLID Principles”SOLID is an acronym for five foundational object-oriented design principles, popularized by Robert C. Martin (“Uncle Bob”). Together, they guide developers toward writing code that is easy to reason about and change.
S - Single Responsibility Principle (SRP)
Section titled “S - Single Responsibility Principle (SRP)”“A class should have one, and only one, reason to change.”
Every module, class, or function should be responsible for exactly one thing. If a class handles both business logic and database persistence, it has two reasons to change - violating SRP.
Bad: A UserService class that validates a user form, saves the user to a database, and sends a welcome email.
Good: Separate UserValidator, UserRepository, and EmailService classes, each with a single job.
O - Open/Closed Principle (OCP)
Section titled “O - Open/Closed Principle (OCP)”“Software entities should be open for extension, but closed for modification.”
You should be able to add new behavior to a system without changing its existing, tested code. This is typically achieved through abstractions, interfaces, and polymorphism. Adding a new feature means writing new code, not editing old code.
L - Liskov Substitution Principle (LSP)
Section titled “L - Liskov Substitution Principle (LSP)”“Objects of a subclass should be substitutable for objects of the parent class without breaking the application.”
If Bird has a fly() method and Penguin extends Bird, substituting a Penguin where a Bird is expected should not break anything - but it will, since penguins can’t fly. This signals a flawed inheritance hierarchy. LSP forces you to design inheritance trees that are semantically correct.
I - Interface Segregation Principle (ISP)
Section titled “I - Interface Segregation Principle (ISP)”“A class should not be forced to implement interfaces it does not use.”
Prefer many small, specific interfaces over one large, monolithic “catch-all” interface. A Printer class shouldn’t be forced to implement a fax() method just because it implements a broad MachineInterface.
D - Dependency Inversion Principle (DIP)
Section titled “D - Dependency Inversion Principle (DIP)”“Depend on abstractions, not concretions.”
High-level modules (business logic) should not depend on low-level modules (database drivers, HTTP clients). Both should depend on abstractions (interfaces). This makes it easy to swap implementations - for example, swapping a MySQL database for PostgreSQL - without changing business logic.
| Principle | Core Idea |
|---|---|
| Single Responsibility | One class, one job |
| Open/Closed | Extend behavior; don’t modify existing code |
| Liskov Substitution | Subtypes must be safely substitutable |
| Interface Segregation | Small, focused interfaces |
| Dependency Inversion | Depend on abstractions, not implementations |
The Three Universal Heuristics
Section titled “The Three Universal Heuristics”Beyond SOLID, three additional principles are universally valued across all programming paradigms and languages.
DRY - Don’t Repeat Yourself
Section titled “DRY - Don’t Repeat Yourself”“Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.”
If the same logic appears in two places, it will inevitably get updated in one place and forgotten in another, creating inconsistency and bugs. DRY is not just about avoiding copy-pasted code - it’s about having a single source of truth for every business rule and piece of knowledge.
Violated by: Copy-pasting validation logic into three different controllers instead of creating a shared validate() function.
KISS - Keep It Simple, Stupid
Section titled “KISS - Keep It Simple, Stupid”“Most systems work best if they are kept simple rather than made complex.”
Complexity is the enemy of reliability. Always prefer the simplest solution that solves the problem. Clever, overly-engineered code is harder to debug, harder to test, and harder for the next developer to understand.
Violated by: Using a complex abstract factory method pattern when a simple function would suffice.
YAGNI - You Aren’t Gonna Need It
Section titled “YAGNI - You Aren’t Gonna Need It”“Don’t add functionality until you actually need it.”
Avoid building features “just in case” they might be useful in the future. Speculative generality adds complexity, increases maintenance burden, and often turns out to be wrong anyway. Build what is necessary now; refactor and extend when the need arises.
Violated by: Building a complex plugin system on day one for a simple script, anticipating future extensibility that never materializes.
Separation of Concerns (SoC)
Section titled “Separation of Concerns (SoC)”Separation of Concerns is a broad design principle that states that a software system should be divided into distinct sections, where each section addresses a separate concern (a specific aspect of the program’s functionality).
Modern web frameworks embody this principle naturally:
- Model: Data and business logic (the “what”)
- View: Presentation/UI (the “how it looks”)
- Controller: Request handling (the “how it connects”)
This separation means a designer can update the View without touching business logic, and a backend developer can change data models without affecting the UI.