β‘
Design Principles
| Acronym | Full Name | What | Why | When |
|---|---|---|---|---|
| S | Single Responsibility Principle | A class should have only one reason to change. Each module should be responsible for a single user, actor, or role. | Improves maintainability, testability, and modularity. Reduces coupling and makes code changes less risky. | When designing class responsibilities and determining where to place business logic. |
| O | Open/Closed Principle | Software entities should be open for extension but closed for modification. Add new behavior through extension, not by changing existing code. | Reduces risk of breaking existing functionality when adding new features. Encourages flexible design. | When you need to add new features without touching stable code. Use interfaces and inheritance. |
| L | Liskov Substitution Principle | Derived classes must be substitutable for their base classes without breaking the contract. Objects of derived classes should work seamlessly where base class objects are expected. | Ensures type safety and prevents unexpected runtime errors. Enables polymorphism to work correctly. | When designing inheritance hierarchies and interface implementations. Check that subtypes honor parent contracts. |
| I | Interface Segregation Principle | Clients should not be forced to depend on interfaces they do not use. Prefer many small, specific interfaces over large, general ones. | Reduces coupling between components. Clients only depend on what they actually need. | When defining interfaces and abstract contracts. Split large interfaces into smaller, focused ones. |
| D | Dependency Inversion Principle | High-level modules should not depend on low-level modules; both should depend on abstractions. Depend on abstractions, not concrete implementations. | Decouples layers and components. Makes testing easier through mocking and dependency injection. | When organizing application architecture. Use DI containers to manage dependencies. |
| DRY | Don't Repeat Yourself | Every piece of knowledge should have a single, unambiguous representation in the system. Avoid duplicating logic, data, or intent. | Reduces bugs by centralizing logic. Changes only need to happen in one place. Improves maintainability. | When writing code, refactor duplication into reusable functions, classes, or utilities. |
| KISS | Keep It Simple, Stupid | Design and implement using the simplest approach that solves the problem. Avoid unnecessary complexity, abstraction, or sophistication. | Simpler code is easier to understand, debug, and maintain. Reduces cognitive load. | During design and implementation. Choose straightforward solutions before considering complex alternatives. |
| YAGNI | You Aren't Gonna Need It | Do not add functionality until it is explicitly required. Avoid speculative development of features "just in case." | Reduces code bloat and maintenance burden. Keeps the codebase lean and focused. | When tempted to add "future-proofing" features. Build only what is needed now. |
| SoC | Separation of Concerns | Divide the program into distinct sections, each handling a specific concern or responsibility. Different parts should manage different aspects (UI, business logic, data access). | Improves modularity, testability, and reusability. Makes code easier to understand and modify. | In overall application architecture. Separate presentation, business logic, and data persistence layers. |
| LoD | Law of Demeter | An object should only communicate with its direct dependencies, not with the dependencies of its dependencies. Avoid chained method calls like obj.a().b().c(). | Reduces coupling and brittleness. Changes to intermediate objects don't break callers. | When designing object interactions and method interfaces. Keep call chains shallow. |
| CQS | Command Query Separation | Methods should either perform an action (Command) or return data (Query), not both. Separate methods that modify state from those that retrieve state. | Improves clarity and predictability. Easier to reason about side effects. Enables better caching and optimization. | When designing public interfaces and APIs. Use distinct methods for reading and writing. |
| CoC | Convention over Configuration | Provide sensible defaults and follow established conventions rather than requiring explicit configuration for common scenarios. | Reduces boilerplate and setup time. Makes projects more consistent and easier to navigate. | When designing frameworks and libraries. Establish conventions that developers can rely on. |
| PIE | Program to Interfaces, not Implementations | Code against abstract interfaces rather than concrete implementations. This enables loose coupling and polymorphism. | Makes code more flexible and testable. Allows swapping implementations without changing client code. | When designing dependencies and contracts. Use interfaces for external dependencies. |
| PoLA | Principle of Least Astonishment | Design systems so they behave as users expect. Avoid surprising behavior or unintuitive naming. Be consistent with similar components. | Reduces cognitive load and errors. Makes code more predictable and easier to use. | When designing APIs, naming conventions, and behavior. Consider developer expectations. |
π
Coding Standards
π―
Paradigms
ποΈ
Architectural Patterns
π¨
Design Patterns
π¨Creational PatternsObject creation mechanisms
Singleton
Creational
Ensure a class has only one instance and provide a global point of access to it. Use for loggers, configuration managers, connection pools.
When you need exactly one instance shared globally. Be careful of threading issues.
Factory Method
Creational
Define an interface for creating objects, letting subclasses decide which class to instantiate. Reduces coupling to concrete classes.
When object creation logic is complex or depends on runtime conditions.
Abstract Factory
Creational
Provide an interface for creating families of related objects. Ensures consistency across a set of related products.
When you need to create related object families (e.g., UI elements for different themes).
Builder
Creational
Separate construction of complex objects from their representation. Build objects step by step. Improves readability for objects with many parameters.
When constructing complex objects with many optional parameters or validation requirements.
Prototype
Creational
Specify kinds of objects to create using a prototype instance, cloning it rather than creating from scratch. Efficient for expensive object creation.
When object creation is expensive or when you need deep copies.
Object Pool
Creational
Reuse objects that are expensive to create by pooling them. Acquire from pool, use, return. Reduces allocation overhead.
When object creation is expensive and instances are used briefly then released.
ποΈStructural PatternsObject composition and relationships
Adapter
Structural
Convert the interface of a class into another interface clients expect. Bridge incompatible interfaces. Also called Wrapper.
When integrating third-party libraries or legacy code with incompatible interfaces.
Decorator
Structural
Attach additional responsibilities to an object dynamically, extending functionality without modifying the object. Flexible alternative to subclassing.
When you need to add behavior to individual objects without affecting others or creating new subclasses.
Proxy
Structural
Provide a surrogate or placeholder for another object to control access to it. Can add lazy loading, logging, or access control.
When you need to control access, defer creation, or add cross-cutting concerns.
Facade
Structural
Provide a unified, simplified interface to a set of interfaces in a subsystem. Hides complexity and reduces coupling.
When you want to simplify complex subsystems or provide a simpler API.
Composite
Structural
Compose objects into tree structures representing part-whole hierarchies. Clients treat individual objects and compositions uniformly.
When building tree structures (e.g., menu systems, document hierarchies).
Bridge
Structural
Decouple an abstraction from its implementation so they can vary independently. Avoid cartesian product of abstractions Γ implementations.
When you have multiple dimensions of variation and want to avoid explosion of subclasses.
βοΈBehavioral PatternsObject collaboration and responsibility distribution
Strategy
Behavioral
Define a family of algorithms, encapsulate each one, and make them interchangeable. Clients can select strategies at runtime.
When you have multiple algorithms for a task and need to switch between them.
Observer
Behavioral
Define a one-to-many dependency so when one object changes state, all dependents are notified automatically. Implements publish-subscribe.
When you need loose coupling between components that must react to state changes.
Command
Behavioral
Encapsulate a request as an object, allowing you to parameterize clients with different requests, queue requests, and log/undo operations.
When you need undo/redo, queuing, or command logging.
Chain of Responsibility
Behavioral
Avoid coupling the sender of a request to its receiver by giving multiple objects a chance to handle it. Pass request along a chain.
When processing logic depends on runtime conditions or should be extensible.
Mediator
Behavioral
Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly.
When multiple objects need to communicate in complex ways and you want to centralize that logic.
State
Behavioral
Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.
When an object's behavior depends on state and you have many conditional branches.
Template Method
Behavioral
Define the skeleton of an algorithm in a base class, letting subclasses fill in specific steps. Inverts control of algorithm steps.
When you have an algorithm with varying implementations of certain steps.
Visitor
Behavioral
Represent an operation to be performed on elements of an object structure. Lets you define new operations without changing the classes of those elements.
When you need to perform operations on complex object structures and operations change frequently.
Iterator
Behavioral
Provide a way to access elements of a collection sequentially without exposing its underlying representation.
When you need to traverse collections in various ways without exposing their internal structure.
Memento
Behavioral
Capture and externalize an object's internal state without violating encapsulation, and restore the object to this state later.
When you need to implement undo functionality or save/restore object state.
π’
Enterprise Patterns
πΎ
Data Handling
π€
CQRS & Event Sourcing
π‘Event Sourcing Example: Instead of storing final state, store sequence of events (OrderCreated, PaymentProcessed, OrderShipped). Rebuild state by replaying events.
ποΈ
Database Patterns
π
API Patterns
π¬
Messaging & Events
βοΈ
Concurrency
π§ͺ
Testing
πΊTesting Pyramid: 70% unit tests, 20% integration tests, 10% E2E. Many fast unit tests, fewer slow E2E tests.
β
Error Handling
π
Security
π
Observability
β‘
Performance
π
Deployment
#
.NET/C# Specific
πKey .NET libraries: Entity Framework Core (ORM), Dapper (Micro-ORM), MassTransit (Messaging), NUnit/xUnit (Testing), Serilog (Logging).
π
Python Specific
πKey Python ecosystem: pytest (Testing), sqlalchemy (ORM), httpx (HTTP client), uvicorn (ASGI server), black (Formatter), ruff (Linter).
π¦
Rust Specific
πKey Rust ecosystem: tokio (Async runtime), axum (Web framework), sqlx (Database), serde (Serialization), tracing (Logging), uuid (IDs).