Design principles offer a shared vocabulary for evaluating trade-offs in how code is structured, drawn from years of recurring observations about what makes code maintainable across long-running projects. Principles guide design decisions, but they do not replace them. Context still matters, since principles are guidelines, not absolute rules.
The Single Responsibility Principle states that a class, module, or function should have only one reason to change. A “responsibility” is an axis of change, traceable to a single stakeholder, team, or set of rules. Grouping code by what changes for the same reason keeps unrelated concerns from rippling into each other.
CLASS UserValidator:FUNCTION validateUser(user)CLASS UserProcessor:FUNCTION applyBusinessRules(user)CLASS UserRepository:FUNCTION saveToDatabase(user)
The Open/Closed Principle states that software entities should be open for extension but closed for modification. New behavior should be added through new code rather than by editing existing code, so working code stays untouched and at less risk of breaking. A common shape is a stable contract with new implementations added behind it.
CLASS EmailNotification:FUNCTION send() -- delivers via emailCLASS SMSNotification:FUNCTION send() -- delivers via SMSFUNCTION sendNotification(notification):notification.send()
The Liskov Substitution Principle states that objects of a derived class should be substitutable for objects of the base class without affecting program correctness. A subclass must honor every promise the parent makes; otherwise, code that holds the parent type can break unexpectedly when handed a subclass instance.
The Interface Segregation Principle states that clients should not be forced to depend on interfaces or methods they do not use. Splitting a broad interface along the natural boundaries of how its consumers use it keeps each implementation focused and avoids stub methods that throw “not supported” for behavior they cannot meaningfully provide.
The Dependency Inversion Principle states that high-level modules should not depend on low-level modules; both should depend on abstractions. Placing a stable abstraction between the two means the underlying implementation can change without disturbing the logic above it. This thinking underpins testable, swappable components and the layered architectures built on them.
The DRY (Don’t Repeat Yourself) principle states that every piece of knowledge should have a single, unambiguous representation in a system. When the same business rule lives in multiple places, requirement changes can update one and miss the others, letting the system silently drift apart over time.
FUNCTION isAboveMinimumAge(age):IF age IS LESS THAN 18: RETURN falseRETURN true
The KISS principle (Keep It Simple, Stupid) emphasizes that systems work best when kept simple rather than made complicated. A five-layer abstraction for a problem one utility function would solve adds complexity without real benefit. The simpler solution that fully addresses the problem is usually the better solution.
The YAGNI (You Aren’t Gonna Need It) principle states that functionality should not be added until it is necessary. Building a plugin system before anyone needs plugins, or designing for multi-tenancy before the first tenant exists, means carrying complexity today for value that may never materialize. This kind of premature build is sometimes called speculative generality.