Best Practices Decision Framework
When to Use This Framework
Use this framework for daily development decisions: designing classes, reviewing code, refactoring legacy code.
Decision Tree: Which SOLID Principle to Apply?
Is this a new class/interface?
|- YES: Apply SRP -> One reason to change
| +- Will it need variations?
| |- YES: Apply OCP -> Design for extension
| | +- Using inheritance?
| | |- YES: Check LSP -> Ensure substitutability
| | +- NO: Continue
| +- NO: Keep simple (YAGNI)
|
+- NO: Modifying existing code?
+- Does class do too much?
|- YES: Refactor toward SRP
+- NO: Adding variation?
|- YES: Apply OCP -> Extend, don't modify
+- NO: Continue
Does this interface have > 5 methods?
|- YES: Do all clients use all methods?
| |- NO: Apply ISP -> Split interface
| +- YES: Keep cohesive interface
+- NO: Continue
Does this class create its dependencies?
|- YES: Is it at an architectural boundary?
| |- YES: Apply DIP -> Inject dependencies
| +- NO: Internal detail -> OK to keep simple
+- NO: Continue
Decision Matrix: Apply SOLID or Stay Simple?
| Factor | Apply SOLID | Stay Simple |
|---|---|---|
| Team size | > 3 developers | Solo or pair |
| Codebase size | > 10k lines | < 1k lines |
| Change frequency | Weekly releases | Yearly releases |
| Expected lifetime | Years | Weeks (prototype) |
| Domain complexity | High (finance, healthcare) | Low (CRUD app) |
| Testability requirement | High (CI/CD, TDD) | Low (manual testing) |
| API/Library | Public API | Internal use only |
Security Best Practices
DIP for Security: - Don't create security-critical dependencies (auth, crypto) directly - Inject vetted implementations (bcrypt, jwt libraries) - Abstract authentication to enable security updates without business logic changes
Example (PHP):
// WRONG: Direct dependency on crypto implementation
class UserService {
public function hashPassword($password) {
return md5($password); // INSECURE, hard to change
}
}
// CORRECT: Depend on abstraction
interface IPasswordHasher {
public function hash(string $password): string;
public function verify(string $password, string $hash): bool;
}
class BcryptPasswordHasher implements IPasswordHasher {
public function hash(string $password): string {
return password_hash($password, PASSWORD_BCRYPT);
}
public function verify(string $password, string $hash): bool {
return password_verify($password, $hash);
}
}
class UserService {
public function __construct(private IPasswordHasher $hasher) {}
public function hashPassword(string $password): string {
return $this->hasher->hash($password);
}
}
// Can upgrade to Argon2 by injecting new implementation
SRP for Security Auditing:
- Isolate security-critical code in dedicated classes
- Easier to audit, test, and secure
- Example: AuthenticationService, AuthorizationService, EncryptionService
Performance Best Practices
Myth: SOLID principles hurt performance. Reality: Maintainable code is easier to optimize.
Performance Considerations:
- Profile before optimizing: Don't sacrifice SOLID for micro-optimizations without data
- Hot path exceptions: In proven bottlenecks, document trade-offs
- Lazy loading: DIP enables lazy initialization of expensive dependencies
- Caching: OCP enables adding caching decorators without modifying core logic
Example (TypeScript):
// DIP enables performance optimization via decorator
interface IUserRepository {
findById(id: string): User;
}
class DatabaseUserRepository implements IUserRepository {
findById(id: string): User {
// Expensive database call
}
}
// Add caching without modifying original
class CachedUserRepository implements IUserRepository {
constructor(
private repository: IUserRepository,
private cache: Map<string, User>
) {}
findById(id: string): User {
if (this.cache.has(id)) {
return this.cache.get(id)!;
}
const user = this.repository.findById(id);
this.cache.set(id, user);
return user;
}
}
// Swap implementations based on needs
const repo = isDevelopment
? new DatabaseUserRepository()
: new CachedUserRepository(new DatabaseUserRepository(), new Map());
Development Standards
Prefer composition over inheritance: - Inheritance creates tight coupling - Composition is flexible (swap at runtime) - Use inheritance only for true "is-a" relationships
Dependency Injection Standards: - Constructor injection for required dependencies - Setter injection for optional dependencies - Avoid Service Locator (hides dependencies)
Interface Design Standards: - Interfaces should be client-focused (ISP) - Abstract enough to support multiple implementations (DIP) - Small and cohesive (ISP)
Testing Standards: - If you can't mock a dependency -- DIP violation - If test setup is complex -- SRP violation (too many dependencies) - If changing one feature breaks unrelated tests -- OCP/SRP violation
Anti-Pattern Avoidance Checklist
Before committing code:
- No god objects (> 300 lines or > 10 methods per class)
- No switch/case on type (use polymorphism)
- No empty method overrides (LSP violation)
- No new keyword in business logic (inject dependencies)
- No methods implementing interface they don't use (ISP)
- No NotImplementedError in production code (design flaw)
Quick Reference: SOLID in One Sentence Each
| Principle | One-Sentence Rule |
|---|---|
| SRP | A class should have one reason to change. |
| OCP | Extend behavior without modifying existing code. |
| LSP | Subtypes must be substitutable for their base types. |
| ISP | Clients shouldn't depend on methods they don't use. |
| DIP | Depend on abstractions, not concrete implementations. |