Best Practices Decision Framework
When to Use
When making architectural decisions about abstraction, duplication, and code organization.
The DRY Decision Tree
-- Code looks duplicated
|
|-- Does it represent the same KNOWLEDGE?
| |-- No -> Keep separate (incidental duplication)
| +-- Yes
|
|-- How many instances?
| |-- 1-2 -> Keep duplicated (WET/AHA approach)
| +-- 3+
|
|-- Are requirements stable?
| |-- No -> Keep duplicated until stable
| +-- Yes
|
|-- Do all instances always change together?
| |-- No -> Might be incidental despite similarity
| +-- Yes
|
+-- Can you create simple abstraction?
|-- No -> Keep duplicated (wrong abstraction worse than duplication)
+-- Yes -> Abstract it (DRY)
Core Principles
- DRY is about knowledge, not code -- Same intent = violates DRY, even if different syntax
- Prefer duplication over wrong abstraction -- Wrong abstraction is harder to fix than duplication
- Wait for Rule of Three -- Don't abstract until third instance (usually)
- Let patterns emerge -- Understand variance before abstracting (AHA principle)
- Context matters -- Microservices, layers, domains may need independent representations
- Abstractions have cost -- Indirection, complexity, cognitive load; balance against benefit
Security Best Practices
| Practice | Why | How |
|---|---|---|
| Never duplicate security logic | Inconsistencies create vulnerabilities | Centralize auth, validation, sanitization |
| Single source for access control | Multiple implementations -> gaps in enforcement | Use policy engine or authorization service |
| Validate at every boundary | Frontend validation is UX; backend is security | DRY the schema, not the validation call |
| Schema-driven input validation | Prevents injection, ensures consistency | JSON Schema, Zod, Protocol Buffers |
| Centralized secret management | Secrets scattered in code -> leaks | Use secrets manager (Vault, AWS Secrets Manager) |
# SECURITY ANTI-PATTERN: Duplicated authorization logic
# routes/admin.py
if user.role != 'admin':
raise Forbidden()
# routes/reports.py
if user.role != 'admin':
raise Forbidden()
# Problem: Easy to forget check, inconsistent enforcement
# BETTER: Centralized authorization
from functools import wraps
def require_role(role):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
if g.user.role != role:
raise Forbidden()
return func(*args, **kwargs)
return wrapper
return decorator
@require_role('admin')
def admin_dashboard():
pass
@require_role('admin')
def view_reports():
pass
# Single source of truth for authorization logic
Performance Best Practices
| Practice | Why | How |
|---|---|---|
| Extract repeated expensive operations | Avoid redundant computation | Cache, memoize, or compute once |
| DRY database queries | Reduces N+1 queries, improves performance | Query utilities, ORM with eager loading |
| Shared constants for thresholds | Ensures consistent behavior, easy tuning | Config files, environment variables |
| Avoid over-abstraction in hot paths | Indirection has cost; inline when necessary | Profile, measure, inline critical sections |
// PERFORMANCE ANTI-PATTERN: Repeated expensive computation
function getUserOrders(userId) {
const user = database.query('SELECT * FROM users WHERE id = ?', userId);
const orders = database.query('SELECT * FROM orders WHERE user_id = ?', userId);
return { user, orders };
}
function getUserPayments(userId) {
const user = database.query('SELECT * FROM users WHERE id = ?', userId);
const payments = database.query('SELECT * FROM payments WHERE user_id = ?', userId);
return { user, payments };
}
// User fetched twice! N+1 problem if called in loop
// BETTER: DRY with caching
const userCache = new Map();
function getUser(userId) {
if (!userCache.has(userId)) {
userCache.set(userId, database.query('SELECT * FROM users WHERE id = ?', userId));
}
return userCache.get(userId);
}
function getUserOrders(userId) {
return {
user: getUser(userId),
orders: database.query('SELECT * FROM orders WHERE user_id = ?', userId),
};
}
Development Standards
| Standard | Rationale | Example |
|---|---|---|
| Single source for business rules | Changes apply consistently | Validation schemas, policy engines |
| DRY configuration, WET implementation | Config should be DRY; implementations can vary | Shared defaults, environment overrides |
| Code generation over manual sync | Manual sync always drifts | OpenAPI -> clients, Protobuf -> types |
| Separate models per boundary | Coupling through shared schema is fragile | DTOs, view models, domain entities |
| Composition over inheritance | Inheritance creates deep coupling | Interfaces, traits, mixins |
When to Refactor Duplication
Immediate refactoring needed when:
- Security logic is duplicated (authorization, validation, sanitization)
- Business rules duplicated across layers (frontend, backend, database)
- Third+ instance appears and pattern is clear
- Bug found in duplicated code (fix reveals divergence)
Defer refactoring when:
- Only 1-2 instances exist
- Requirements still evolving rapidly
- Duplication is incidental (similar syntax, different purpose)
- Abstraction would be complex and unclear
Red flags -- existing abstraction might be wrong:
- Lots of conditionals or special cases in abstraction
- Callers pass
null,undefined, or empty values frequently - Changes always require modifying the abstraction
- Abstraction has many configuration options/parameters
- Understanding abstraction requires reading multiple files
Common Mistakes
- Dogmatic DRY enforcement — Ignores context, creates wrong abstractions
- Ignoring security duplication — Leads to vulnerabilities from inconsistent enforcement
- Premature optimization via abstraction — Measure first; abstractions have cost
- Never refactoring WET code — Duplication becomes entrenched, causes divergence
- Treating all duplication equally — Some duplication (incidental, across boundaries) is acceptable