← Назад к вопросам

Зачем нужен принцип DRY?

1.3 Junior🔥 241 комментариев
#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Зачем нужен принцип 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

  1. Проще исправлять баги — исправляем в одном месте
  2. Проще тестировать — тестируем общую функцию один раз
  3. Проще добавлять функционал — расширяем один кусок кода
  4. Лучше читаемость — код понятнее, нет дублирования
  5. Проще рефакторить — все изменения в одном месте
  6. Меньше памяти — меньше кода в памяти

Когда НЕ применять 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, надёжным и легче расширяемым.

Зачем нужен принцип DRY? | PrepBro