Table of Contents
-
KISS (Keep It Simple, Stupid)
-
YAGNI (You Aren't Gonna Need It)
-
SOLID Principles
-
Quick Reference
-
When Principles Conflict
-
Integration with Code Review
Code Quality Principles
Guidance on KISS, YAGNI, and SOLID principles with language-specific examples.
When To Use
-
Improving code readability and maintainability
-
Applying SOLID, KISS, YAGNI principles during refactoring
When NOT To Use
-
Throwaway scripts or one-time data migrations
-
Performance-critical code where readability trades are justified
KISS (Keep It Simple, Stupid)
Principle: Avoid unnecessary complexity. Prefer obvious solutions over clever ones.
Guidelines
Prefer Avoid
Simple conditionals Complex regex for simple checks
Explicit code Magic numbers/strings
Standard patterns Clever shortcuts
Direct solutions Over-abstracted layers
Python Example
Bad: Overly clever one-liner
users = [u for u in (db.get(id) for id in ids) if u and u.active and not u.banned]
Good: Clear and readable
users = [] for user_id in ids: user = db.get(user_id) if user and user.active and not user.banned: users.append(user)
Rust Example
// Bad: Unnecessary complexity fn process(data: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> { data.iter() .map(|&b| b.checked_add(1).ok_or("overflow")) .collect::<Result<Vec<_>, _>>() .map_err(|e| e.into()) }
// Good: Simple and clear fn process(data: &[u8]) -> Result<Vec<u8>, &'static str> { let mut result = Vec::with_capacity(data.len()); for &byte in data { result.push(byte.checked_add(1).ok_or("overflow")?); } Ok(result) }
YAGNI (You Aren't Gonna Need It)
Principle: Don't implement features until they are actually needed.
Guidelines
Do Don't
Solve current problem Build for hypothetical futures
Add when 3rd use case appears Create abstractions for 1 use case
Delete dead code Keep "just in case" code
Minimal viable solution Premature optimization
Python Example
Bad: Premature abstraction for one use case
class AbstractDataProcessor: def process(self, data): ... def validate(self, data): ... def transform(self, data): ...
class CSVProcessor(AbstractDataProcessor): def process(self, data): return self.transform(self.validate(data))
Good: Simple function until more cases appear
def process_csv(data: list[str]) -> list[dict]: return [parse_row(row) for row in data if row.strip()]
TypeScript Example
// Bad: Over-engineered config system interface ConfigProvider<T> { get<K extends keyof T>(key: K): T[K]; set<K extends keyof T>(key: K, value: T[K]): void; watch<K extends keyof T>(key: K, callback: (v: T[K]) => void): void; }
// Good: Simple config for current needs const config = { apiUrl: process.env.API_URL || 'http://localhost:3000', timeout: 5000, };
SOLID Principles
Single Responsibility Principle
Each module/class should have one reason to change.
Bad: Multiple responsibilities
class UserManager: def create_user(self, data): ... def send_welcome_email(self, user): ... # Email responsibility def generate_report(self, users): ... # Reporting responsibility
Good: Separated responsibilities
class UserRepository: def create(self, data): ...
class EmailService: def send_welcome(self, user): ...
class UserReportGenerator: def generate(self, users): ...
Open/Closed Principle
Open for extension, closed for modification.
Bad: Requires modification for new types
def calculate_area(shape): if shape.type == "circle": return 3.14 * shape.radius ** 2 elif shape.type == "rectangle": return shape.width * shape.height # Must modify to add new shapes
Good: Extensible without modification
from abc import ABC, abstractmethod
class Shape(ABC): @abstractmethod def area(self) -> float: ...
class Circle(Shape): def init(self, radius: float): self.radius = radius def area(self) -> float: return 3.14 * self.radius ** 2
Liskov Substitution Principle
Subtypes must be substitutable for their base types.
Bad: Violates LSP - Square changes Rectangle behavior
class Rectangle: def set_width(self, w): self.width = w def set_height(self, h): self.height = h
class Square(Rectangle): # Breaks when used as Rectangle def set_width(self, w): self.width = self.height = w # Unexpected side effect
Good: Separate types with common interface
class Shape(ABC): @abstractmethod def area(self) -> float: ...
class Rectangle(Shape): def init(self, width: float, height: float): ...
class Square(Shape): def init(self, side: float): ...
Interface Segregation Principle
Clients shouldn't depend on interfaces they don't use.
// Bad: Fat interface interface Worker { work(): void; eat(): void; sleep(): void; }
// Good: Segregated interfaces interface Workable { work(): void; }
interface Feedable { eat(): void; }
// Clients only implement what they need class Robot implements Workable { work(): void { /* ... */ } }
Dependency Inversion Principle
Depend on abstractions, not concretions.
Bad: Direct dependency on concrete class
class OrderService: def init(self): self.db = PostgresDatabase() # Tight coupling
Good: Depend on abstraction
from abc import ABC, abstractmethod
class Database(ABC): @abstractmethod def save(self, data): ...
class OrderService: def init(self, db: Database): self.db = db # Injected abstraction
Quick Reference
Principle Question to Ask Red Flag
KISS "Is there a simpler way?" Complex solution for simple problem
YAGNI "Do I need this right now?" Building for hypothetical use cases
SRP "What's the one reason to change?" Class doing multiple jobs
OCP "Can I extend without modifying?" Switch statements for types
LSP "Can subtypes replace base types?" Overridden methods with side effects
ISP "Does client need all methods?" Empty method implementations
DIP "Am I depending on abstractions?" new keyword in business logic
When Principles Conflict
-
KISS vs SOLID: For small projects, KISS wins. Add SOLID patterns as complexity grows.
-
YAGNI vs DIP: Don't add abstractions until you have 2+ implementations.
-
Readability vs DRY: Prefer slight duplication over wrong abstraction.
Integration with Code Review
When reviewing code, check:
-
No unnecessary complexity (KISS)
-
No speculative features (YAGNI)
-
Each class has single responsibility (SRP)
-
No god classes (> 500 lines)
-
Dependencies are injected, not created (DIP)
Verification: Run wc -l <file> to check line counts and grep -c "class " <file> to count classes per file.