DRY and KISS Principles in Python
DRY (Don’t Repeat Yourself)
The DRY principle states that every piece of knowledge or logic should have a single, unambiguous representation within a system. This reduces code duplication and makes maintenance easier.
Bad Examples (Not DRY)
# Example 1: Repeated validation logic
def validate_user(user_data):
if len(user_data['username']) < 3:
raise ValueError("Username must be at least 3 characters")
if not user_data['email'].contains('@'):
raise ValueError("Invalid email format")
def validate_admin(admin_data):
if len(admin_data['username']) < 3:
raise ValueError("Username must be at least 3 characters")
if not admin_data['email'].contains('@'):
raise ValueError("Invalid email format")
# Example 2: Repeated calculation
def calculate_circle_area(radius):
return 3.14159 * radius * radius
def calculate_cylinder_volume(radius, height):
return 3.14159 * radius * radius * height
Good Examples (DRY)
# Example 1: Centralized validation logic
def validate_credentials(data):
if len(data['username']) < 3:
raise ValueError("Username must be at least 3 characters")
if not data['email'].contains('@'):
raise ValueError("Invalid email format")
def validate_user(user_data):
validate_credentials(user_data)
def validate_admin(admin_data):
validate_credentials(admin_data)
# Example 2: Using constants and helper functions
import math
def calculate_circle_area(radius):
return math.pi * radius ** 2
def calculate_cylinder_volume(radius, height):
return calculate_circle_area(radius) * height
More DRY Examples
# Using decorators to avoid repetition
from functools import wraps
def log_execution(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Executing {func.__name__}")
result = func(*args, **kwargs)
print(f"Finished {func.__name__}")
return result
return wrapper
@log_execution
def process_data():
pass
@log_execution
def analyze_results():
pass
# Using inheritance to avoid repetition
class BaseValidator:
def validate_age(self, age):
if age < 0 or age > 120:
raise ValueError("Invalid age")
def validate_email(self, email):
if '@' not in email:
raise ValueError("Invalid email")
class UserValidator(BaseValidator):
def validate(self, user_data):
self.validate_age(user_data['age'])
self.validate_email(user_data['email'])
class AdminValidator(BaseValidator):
def validate(self, admin_data):
self.validate_age(admin_data['age'])
self.validate_email(admin_data['email'])
KISS (Keep It Simple, Stupid)
The KISS principle advocates for simplicity in design and implementation. Code should be simple to understand and maintain.
Bad Examples (Not KISS)
# Overcomplicated solution
def get_even_numbers(numbers):
result = []
for i in range(len(numbers)):
current_number = numbers[i]
if current_number % 2 == 0:
result.append(current_number)
return sorted(result, key=lambda x: -x)
# Overly complex class structure
class NumberProcessor:
def __init__(self):
self.numbers = []
def add_number(self, number):
self.numbers.append(number)
def get_number_at_index(self, index):
return self.numbers[index]
def process_numbers(self):
return sum(self.numbers)
Good Examples (KISS)
# Simple and clear solution
def get_even_numbers(numbers):
return [n for n in numbers if n % 2 == 0]
# Simple function instead of complex class
def process_numbers(numbers):
return sum(numbers)
More KISS Examples
# Complex way (avoid)
def get_user_status(user):
status = ""
if user.is_active:
if user.is_premium:
if user.subscription_expired:
status = "Premium (Expired)"
else:
status = "Premium"
else:
if user.free_trial:
status = "Trial"
else:
status = "Basic"
else:
status = "Inactive"
return status
# Simple way (better)
def get_user_status(user):
if not user.is_active:
return "Inactive"
if user.is_premium:
return "Premium (Expired)" if user.subscription_expired else "Premium"
return "Trial" if user.free_trial else "Basic"
# Complex way (avoid)
class DateFormatter:
def __init__(self, date):
self.date = date
self.formatted = None
def format_date(self):
self.formatted = self.date.strftime("%Y-%m-%d")
return self
def get_formatted_date(self):
if self.formatted is None:
self.format_date()
return self.formatted
# Simple way (better)
def format_date(date):
return date.strftime("%Y-%m-%d")
Common Patterns Combining DRY and KISS
# Configuration management
class Config:
_instance = None
@classmethod
def get_instance(cls):
if cls._instance is None:
cls._instance = cls()
return cls._instance
def __init__(self):
self.settings = {
'timeout': 30,
'retries': 3,
'base_url': 'https://api.example.com'
}
# Error handling
def handle_exceptions(func):
@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except ValueError as e:
print(f"Validation error: {e}")
except Exception as e:
print(f"Unexpected error: {e}")
return wrapper
@handle_exceptions
def process_user_data(data):
# Process data here
pass
Best Practices
-
For DRY:
- Create utility functions for common operations
- Use inheritance for shared behavior
- Implement decorators for cross-cutting concerns
- Centralize configuration and constants
-
For KISS:
- Write small, focused functions
- Avoid unnecessary abstractions
- Use clear, descriptive names
- Favor readability over cleverness
-
General:
- Comment only when necessary
- Use built-in Python features
- Follow the standard library’s conventions
- Write self-documenting code
When to Break These Principles
Sometimes it’s okay to break these principles:
-
DRY:
- When duplication is simpler than abstraction
- When shared code would create tight coupling
- For temporary or prototype code
-
KISS:
- When performance is critical
- When implementing complex algorithms
- When following established design patterns
Interview angle
- “When is DRY wrong?” - when the duplication is coincidental rather than semantic. Two pieces of code that look alike but change for different reasons should stay separate; unifying them creates a coupling that fights every future change. The rule is to abstract on the third occurrence, not the second.
- “What does KISS mean concretely?” - choose the solution a colleague can understand and modify without asking you. Cleverness that saves three lines and costs an hour of comprehension is a net loss.
- “How does YAGNI interact with extensibility?” - build for today’s requirement with clean boundaries, so tomorrow’s is cheap to add. Speculative generality is the failure it warns against: an abstraction built for a requirement that never arrives is pure cost.