Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Зачем нужен принцип DRY (Don't Repeat Yourself)
Principal DRY — один из фундаментальных принципов в разработке программного обеспечения. Он гласит: Каждая единица знания должна иметь единственное, недвусмысленное, авторитетное представление в системе. Другими словами — избегайте дублирования кода.
Основная идея
Eсли один и тот же код или логика повторяется в нескольких местах, это создаёт проблемы:
- Сложность поддержки (при исправлении нужно менять везде)
- Риск багов (забыли обновить одну копию)
- Усложнение тестирования
- Снижение читаемости кода
Проблема без применения DRY
# Антипаттерн: Одна и та же логика повторяется
class UserValidator:
def validate_email(self, email: str) -> bool:
if not email:
return False
if '@' not in email:
return False
if '.' not in email.split('@')[1]:
return False
return True
def validate_admin_email(self, email: str) -> bool:
if not email:
return False
if '@' not in email:
return False
if '.' not in email.split('@')[1]:
return False
return True # Точно такая же логика!
def validate_notification_email(self, email: str) -> bool:
if not email:
return False
if '@' not in email:
return False
if '.' not in email.split('@')[1]:
return False
return True # И снова та же логика...
Проблема: Если мы найдём баг в валидации, нужно исправить в трёх местах. Высокий риск пропустить одно.
Решение: Применение DRY
# Правильно: Логика валидации в одном месте
import re
class EmailValidator:
EMAIL_REGEX = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
@staticmethod
def validate(email: str) -> bool:
"""Валидация email один раз."""
if not email or not isinstance(email, str):
return False
return re.match(EmailValidator.EMAIL_REGEX, email) is not None
class UserValidator:
def __init__(self):
self.email_validator = EmailValidator()
def validate_email(self, email: str) -> bool:
return self.email_validator.validate(email)
def validate_admin_email(self, email: str) -> bool:
return self.email_validator.validate(email) # Переиспользуем!
def validate_notification_email(self, email: str) -> bool:
return self.email_validator.validate(email) # Переиспользуем!
Преимущество: Логика валидации в одном месте. При исправлении бага меняем только одну функцию.
Практические примеры DRY
Пример 1: Повторяющаяся проверка прав доступа
# ❌ Плохо: Проверка прав повторяется везде
def get_user_profile(user_id: int, current_user_id: int) -> dict:
if current_user_id != user_id:
raise PermissionError("Нет доступа")
return fetch_user(user_id)
def update_user_profile(user_id: int, current_user_id: int) -> dict:
if current_user_id != user_id:
raise PermissionError("Нет доступа")
return save_user(user_id)
def delete_user_account(user_id: int, current_user_id: int) -> None:
if current_user_id != user_id:
raise PermissionError("Нет доступа")
remove_user(user_id)
# ✅ Хорошо: Логика в одном месте
def require_owner_access(func):
"""Декоратор для проверки что пользователь может изменять свой профиль."""
def wrapper(user_id: int, current_user_id: int, *args, **kwargs):
if current_user_id != user_id:
raise PermissionError("Нет доступа")
return func(user_id, current_user_id, *args, **kwargs)
return wrapper
@require_owner_access
def get_user_profile(user_id: int, current_user_id: int) -> dict:
return fetch_user(user_id)
@require_owner_access
def update_user_profile(user_id: int, current_user_id: int) -> dict:
return save_user(user_id)
@require_owner_access
def delete_user_account(user_id: int, current_user_id: int) -> None:
remove_user(user_id)
Пример 2: Общие константы и конфигурация
# ❌ Плохо: Магические числа везде
def validate_password(password: str) -> bool:
return len(password) >= 8
def generate_token(length: int = 32) -> str:
if length < 8:
raise ValueError("Минимум 8 символов")
return secrets.token_urlsafe(length)
def check_rate_limit(requests_count: int) -> bool:
return requests_count < 1000 # Магическое число 1000!
# ✅ Хорошо: Константы в одном месте
class Config:
MIN_PASSWORD_LENGTH = 8
MIN_TOKEN_LENGTH = 8
MAX_REQUESTS_PER_HOUR = 1000
def validate_password(password: str) -> bool:
return len(password) >= Config.MIN_PASSWORD_LENGTH
def generate_token(length: int = 32) -> str:
if length < Config.MIN_TOKEN_LENGTH:
raise ValueError(f"Минимум {Config.MIN_TOKEN_LENGTH} символов")
return secrets.token_urlsafe(length)
def check_rate_limit(requests_count: int) -> bool:
return requests_count < Config.MAX_REQUESTS_PER_HOUR
Пример 3: Извлечение общей логики в функцию
# ❌ Плохо: Обработка ошибок повторяется
def save_user(user: dict) -> dict:
try:
result = db.insert('users', user)
return result
except ValidationError as e:
logger.error(f"Ошибка валидации: {e}")
raise
except DatabaseError as e:
logger.error(f"Ошибка БД: {e}")
raise
def save_product(product: dict) -> dict:
try:
result = db.insert('products', product)
return result
except ValidationError as e:
logger.error(f"Ошибка валидации: {e}")
raise
except DatabaseError as e:
logger.error(f"Ошибка БД: {e}")
raise
# ✅ Хорошо: Общая логика в одной функции
def save_to_database(table: str, data: dict) -> dict:
try:
result = db.insert(table, data)
logger.info(f"Успешно сохранено в {table}")
return result
except ValidationError as e:
logger.error(f"Ошибка валидации: {e}")
raise
except DatabaseError as e:
logger.error(f"Ошибка БД: {e}")
raise
def save_user(user: dict) -> dict:
return save_to_database('users', user)
def save_product(product: dict) -> dict:
return save_to_database('products', product)
Преимущества DRY
- Проще исправлять баги — исправляем в одном месте
- Проще тестировать — тестируем общую функцию один раз
- Проще добавлять функционал — расширяем один кусок кода
- Лучше читаемость — код понятнее, нет дублирования
- Проще рефакторить — все изменения в одном месте
- Меньше памяти — меньше кода в памяти
Когда НЕ применять DRY
# Иногда дублирование лучше, чем сложная абстракция
# Если две части кода случайно выглядят одинаково, но логически не связаны,
# лучше оставить их отдельными
# Плохо: Чрезмерная абстракция
def generic_process(data, operation1, operation2):
result = operation1(data)
return operation2(result)
# Хорошо: Явный код для каждого случая
def validate_and_save_user(user_data):
validated = validate_user(user_data)
return save_user(validated)
def parse_and_format_document(doc_data):
parsed = parse_document(doc_data)
return format_document(parsed)
Принцип DRY — это не о том, чтобы избежать дублирования любой ценой, а о том, чтобы избежать дублирования логики и знаний. Это делает код более maintainable, надёжным и легче расширяемым.