Core Software Design Principles: Foundations for Maintainable Code
As projects grow, the challenge isn’t writing code—it’s maintaining it. Code becomes hard to change, one modification cascades through multiple files, and newcomers can’t find where the rules live. These problems almost always stem from a lack of clear, consistent design principles. This post explores five core principles—SSOT, DRY, KISS, YAGNI, and SRP—that form the foundation of maintainable software.
1. Principles at a Glance
| Principle | Focus | Problem Solved |
|---|---|---|
| SSOT | Single source of truth | Prevent inconsistent logic |
| DRY | Avoid repeated implementation | Reduce duplicate code |
| KISS | Keep it simple | Prevent over-engineering |
| YAGNI | Don’t implement prematurely | Avoid unnecessary complexity |
| SRP | Single responsibility | Reduce modification cost |
2. SSOT: Single Source of Truth
Core Idea
Every piece of knowledge must have a single, authoritative source.
SSOT focuses on data consistency and rule ownership, ensuring each “truth” in the system has exactly one definition.
Common Scenarios
- User permissions determined by backend, frontend only displays
- State enums defined in schema, not scattered in code
- Configuration centralized, not hardcoded
Anti-pattern:
// Frontend hardcodes role list
const roles = ["admin", "user", "guest"];
// Backend also defines its own
enum Role {
ADMIN,
USER,
GUEST,
}
Better:
// Backend API returns roles, frontend uses directly
const roles = await fetchRoles();
3. DRY: Don’t Repeat Yourself
Core Idea
The same knowledge should be expressed in exactly one place.
DRY focuses on code structure and logic reuse, eliminating copy-paste patterns.
Common Scenarios
- Duplicate if/else conditions across files
- Repeated calculation logic
- Validation rules scattered throughout the codebase
Anti-pattern:
// Permission check duplicated in multiple places
if (user.role === "admin") {
/* ... */
}
// In another file
if (currentUser.role === "admin") {
/* ... */
}
Better:
// Extract to a utility function
function isAdmin(user: User): boolean {
return user.role === "admin";
}
SSOT vs DRY
| Dimension | SSOT | DRY |
|---|---|---|
| Focus | Source of truth | Implementation |
| Problem | Multiple versions of truth | Duplicate code |
| Level | Architecture/data layer | Code layer |
4. KISS: Keep It Simple, Stupid
Core Idea
If a simple solution works, don’t complicate it.
KISS, coined by engineer Kelly Johnson, emphasizes simplicity above all.
Warning Signs
When you see naming like this, you’ve probably overthought it:
AbstractFactoryStrategyManagerProviderFactory;
Practical Guidelines
- Favor straightforward designs
- Avoid premature abstraction
- Code should be readable to your future self in six months
Anti-pattern:
// Over-engineered: complex pattern for simple config
class ConfigurationStrategyFactory {
createStrategy(type: string): ConfigStrategy {
/* ... */
}
}
Better:
// Simple and direct
const config = {
apiUrl: process.env.API_URL,
timeout: 5000,
};
5. YAGNI: You Aren’t Gonna Need It
Core Idea
Don’t implement features you don’t need yet.
Common Pitfalls
- “We might need multi-tenancy later”
- “Let’s build a plugin architecture now, just in case”
- “Add a config option in case we need to change it”
The result: complexity explodes, and when the real requirement arrives, the original design doesn’t fit anyway.
Anti-pattern:
// Premature optimization: project has single data source
class MultiDatabaseConnectionPool {
private pools: Map<string, ConnectionPool>;
// Complex multi-database management logic...
}
Better:
// Meet current requirements
const db = new Database(process.env.DATABASE_URL);
6. SRP: Single Responsibility Principle
Core Idea
A module or class should have only one reason to change.
Litmus Test
If describing a module’s functionality requires “and” to connect multiple things, it likely violates SRP.
Anti-pattern:
class UserService {
login(credentials: Credentials) {
/* ... */
}
validatePassword(password: string) {
/* ... */
}
sendWelcomeEmail(user: User) {
/* ... */
}
generateReport(user: User) {
/* ... */
}
}
Better:
class AuthService {
login(credentials: Credentials) {
/* ... */
}
validatePassword(password: string) {
/* ... */
}
}
class NotificationService {
sendWelcomeEmail(user: User) {
/* ... */
}
}
class ReportService {
generateUserReport(user: User) {
/* ... */
}
}
7. How These Principles Relate
These principles aren’t isolated—they form a hierarchical constraint system:
┌─────────────────────┐
│ SRP │ ← Define responsibility boundaries
└──────────┬──────────┘
↓
┌─────────────────────┐
│ DRY │ ← Reuse within boundaries
└──────────┬──────────┘
↓
┌─────────────────────┐
│ SSOT │ ← Determine authority
└──────────┬──────────┘
↓
┌─────────────────────┐
│ KISS + YAGNI │ ← Constrain overall complexity
└─────────────────────┘
SRP defines boundaries → DRY enables reuse → SSOT establishes authority → KISS/YAGNI control complexity
8. Engineering Checklist
Turn these principles into daily checkpoints:
- Each rule has exactly one definition location (SSOT)
- Don’t copy rules—reference them (DRY)
- Don’t code for hypothetical futures (YAGNI)
- Code should be obvious to your future self (KISS)
- Each module does one thing (SRP)
9. Further Reading
- Clean Code – Robert C. Martin
- The Pragmatic Programmer – David Thomas & Andrew Hunt
- Refactoring – Martin Fowler
- SOLID Principles
These principles aren’t dogma—they’re engineering wisdom validated over decades. Understanding their spirit matters more than memorizing the rules.