Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое DRY
DRY (Don't Repeat Yourself) — один из фундаментальных принципов разработки программного обеспечения. Он гласит: каждый элемент знаний должен иметь единственное, непротиворечивое и авторитетное представление в системе.
Другими словами: никогда не повторяй один и тот же код, логику или данные в разных местах. Если нужна одна и та же функциональность несколько раз — выноси в отдельную функцию, класс, утилиту или константу.
Основная идея
Любой код, который появляется в нескольких местах, создаёт проблему:
# ❌ Нарушение DRY: код повторяется
def validate_email_for_user_register():
email = input("Enter email: ")
if "@" not in email or "." not in email:
print("Invalid email")
return None
return email
def validate_email_for_user_update():
email = input("Enter email: ")
if "@" not in email or "." not in email:
print("Invalid email")
return None
return email
def validate_email_for_newsletter():
email = input("Enter email: ")
if "@" not in email or "." not in email:
print("Invalid email")
return None
return email
# ПРОБЛЕМЫ:
# - Если нужно изменить валидацию (например, добавить .com/.ru проверку),
# нужно изменить в 3 местах
# - Легко забыть одно место и получить несогласованное поведение
# - Код занимает больше места
# - Сложнее поддерживать и тестировать
# ✅ Следование DRY: функция переиспользуется
def validate_email(email: str) -> bool:
"""Валидирует email адрес"""
return "@" in email and "." in email
def register_user():
email = input("Enter email: ")
if not validate_email(email):
print("Invalid email")
return None
return email
def update_user():
email = input("Enter email: ")
if not validate_email(email):
print("Invalid email")
return None
return email
def subscribe_newsletter():
email = input("Enter email: ")
if not validate_email(email):
print("Invalid email")
return None
return email
# ПРЕИМУЩЕСТВА:
# - Одно место для изменений
# - Согласованное поведение везде
# - Легче тестировать
# - Меньше кода
Уровни нарушения DRY
1. Повтор алгоритма (Algorithm Duplication)
# ❌ Один и тот же алгоритм в разных функциях
def calculate_user_fee():
"""Расчёт комиссии для пользователя"""
fee = amount * 0.1
if amount > 1000:
fee = amount * 0.05
return fee
def calculate_seller_fee():
"""Расчёт комиссии для продавца"""
fee = amount * 0.1
if amount > 1000:
fee = amount * 0.05
return fee
# Логика одинаковая! Если нужно изменить ставку — меняешь в двух местах
# ✅ Выносим логику в одну функцию
def calculate_fee(amount: float, rate: float = 0.1, threshold: float = 1000, reduced_rate: float = 0.05) -> float:
"""Расчёт комиссии"""
return (amount * reduced_rate) if amount > threshold else (amount * rate)
user_fee = calculate_fee(amount=500)
seller_fee = calculate_fee(amount=500)
2. Повтор данных (Data Duplication)
# ❌ Одни и те же данные в разных местах
VALID_STATUSES_IN_MODULE_A = ["pending", "processing", "completed"]
VALID_STATUSES_IN_MODULE_B = ["pending", "processing", "completed"]
VALID_STATUSES_IN_MODULE_C = ["pending", "processing", "completed"]
# Если добавить новый статус, нужно обновить 3 переменные
# ✅ Одно место для данных
VALID_STATUSES = ["pending", "processing", "completed"]
# Используем везде
if status in VALID_STATUSES:
print(f"Status {status} is valid")
3. Повтор логики валидации (Validation Logic)
# ❌ Валидация повторяется везде
class UserService:
def register_user(self, email):
# Валидация в методе
if not email or "@" not in email:
raise ValueError("Invalid email")
# Логика
return User(email)
def update_user(self, user_id, email):
# Та же валидация
if not email or "@" not in email:
raise ValueError("Invalid email")
# Логика
return user.update(email)
class AdminService:
def add_user_to_group(self, email):
# И опять валидация
if not email or "@" not in email:
raise ValueError("Invalid email")
# Логика
return group.add_user(email)
# ✅ Вываливаем валидацию в отдельный объект или декоратор
class EmailValidator:
@staticmethod
def validate(email: str) -> bool:
return email and "@" in email and "." in email
def validate_email_required(func):
def wrapper(email):
if not EmailValidator.validate(email):
raise ValueError("Invalid email")
return func(email)
return wrapper
class UserService:
@validate_email_required
def register_user(self, email):
return User(email)
@validate_email_required
def update_user(self, user_id, email):
return user.update(email)
4. Повтор SQL запросов (Query Duplication)
# ❌ SQL запросы повторяются
class UserRepository:
def get_active_users(self):
return db.execute(
"SELECT * FROM users WHERE status='active' ORDER BY created_at DESC"
)
def get_users_count(self):
return db.execute(
"SELECT COUNT(*) FROM users WHERE status='active'"
)
def get_user_emails(self):
return db.execute(
"SELECT email FROM users WHERE status='active' ORDER BY created_at DESC"
)
# Условие `status='active'` повторяется везде
# ✅ Выносим условие
class UserRepository:
ACTIVE_USER_FILTER = "status='active'"
def get_active_users(self):
return db.execute(f"SELECT * FROM users WHERE {self.ACTIVE_USER_FILTER} ORDER BY created_at DESC")
def get_users_count(self):
return db.execute(f"SELECT COUNT(*) FROM users WHERE {self.ACTIVE_USER_FILTER}")
def get_user_emails(self):
return db.execute(f"SELECT email FROM users WHERE {self.ACTIVE_USER_FILTER} ORDER BY created_at DESC")
5. Повтор стилей и констант (Style Duplication)
# ❌ Стили повторяются в коде
class UserWidget:
def render(self):
return f"""
<div style="padding: 16px; margin: 8px; background: #f5f5f5; border-radius: 8px;">
{self.user.name}
</div>
"""
class ProductWidget:
def render(self):
return f"""
<div style="padding: 16px; margin: 8px; background: #f5f5f5; border-radius: 8px;">
{self.product.name}
</div>
"""
# ✅ Константы и компоненты
CARD_STYLE = "padding: 16px; margin: 8px; background: #f5f5f5; border-radius: 8px;"
class BaseCard:
def render_card(self, content):
return f'<div style="{CARD_STYLE}">{content}</div>'
class UserWidget(BaseCard):
def render(self):
return self.render_card(self.user.name)
class ProductWidget(BaseCard):
def render(self):
return self.render_card(self.product.name)
Практические примеры DRY
Пример 1: Функция vs Код
# ❌ Без DRY
def order_summary():
total = 0
for item in cart:
total += item.price * item.quantity
tax = total * 0.1
return total + tax
def invoice_summary():
total = 0
for item in invoice_items:
total += item.price * item.quantity
tax = total * 0.1
return total + tax
def monthly_report():
total = 0
for item in monthly_items:
total += item.price * item.quantity
tax = total * 0.1
return total + tax
# ✅ С DRY
def calculate_total_with_tax(items, tax_rate=0.1):
subtotal = sum(item.price * item.quantity for item in items)
return subtotal * (1 + tax_rate)
order_total = calculate_total_with_tax(cart)
invoice_total = calculate_total_with_tax(invoice_items)
monthly_total = calculate_total_with_tax(monthly_items)
Пример 2: Конфигурация
# ❌ Без DRY: константы разбросаны
class EmailService:
SMTP_HOST = "smtp.gmail.com"
SMTP_PORT = 587
SENDER_EMAIL = "noreply@example.com"
class BackupService:
EMAIL_SERVER = "smtp.gmail.com"
EMAIL_PORT = 587
FROM_ADDRESS = "noreply@example.com"
class ReportService:
MAIL_HOST = "smtp.gmail.com"
MAIL_PORT = 587
MAIL_FROM = "noreply@example.com"
# ✅ С DRY: одно место для конфигурации
class Config:
SMTP_HOST = "smtp.gmail.com"
SMTP_PORT = 587
SENDER_EMAIL = "noreply@example.com"
class EmailService:
def __init__(self, config: Config):
self.host = config.SMTP_HOST
self.port = config.SMTP_PORT
self.sender = config.SENDER_EMAIL
Пример 3: Мixin для переиспользования
# ❌ Код для сохранения timestamps повторяется
class Article:
def __init__(self):
from datetime import datetime
self.created_at = datetime.now()
self.updated_at = datetime.now()
def save(self):
from datetime import datetime
self.updated_at = datetime.now()
db.save(self)
class Comment:
def __init__(self):
from datetime import datetime
self.created_at = datetime.now()
self.updated_at = datetime.now()
def save(self):
from datetime import datetime
self.updated_at = datetime.now()
db.save(self)
# ✅ С DRY: Mixin
from datetime import datetime
class TimestampMixin:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.created_at = datetime.now()
self.updated_at = datetime.now()
def save(self):
self.updated_at = datetime.now()
super().save()
class Article(TimestampMixin):
def save(self):
super().save()
# Другая логика для Article
class Comment(TimestampMixin):
def save(self):
super().save()
# Другая логика для Comment
Пограничные случаи DRY
# ⚠ ОСТОРОЖНО: не переусложни с DRY
# Иногда два почти одинаковых блока кода лучше оставить как есть,
# если они решают разные задачи и могут развиваться отдельно
def validate_user_email(email):
return "@" in email
def validate_newsletter_email(email):
return "@" in email
# Выглядит как нарушение DRY, но:
# - validate_user_email может добавить требование к длине
# - validate_newsletter_email может требовать подтверждения
# - Они могут эволюционировать по-разному
#
# В этом случае дублирование оправдано, чтобы не создавать
# излишние зависимости между не связанными компонентами
Инструменты для проверки DRY
# Инструменты, которые помогают найти дублирование:
# 1. Code duplication detectors
# - CPDCheck (Copy Paste Detector) — находит копипасты
# - pylint с плагинами — анализирует код
# 2. Статический анализ
# - flake8 — простая проверка
# - pylint — углублённый анализ
# - radon — метрики сложности
# 3. IDE features
# - PyCharm: Analyze -> Run Inspection by Name -> Duplicated Code
# - VSCode: с расширениями
Баланс между DRY и другими принципами
# DRY может конфликтовать с другими принципами:
# 1. DRY vs YAGNI (You Aren't Gonna Need It)
# ❌ Переусложнение для предполагаемого дублирования в будущем
def create_generic_validator(*args, **kwargs):
# Создали сложный валидатор для "будущего дублирования"
# Но это дублирование никогда не произойдёт!
pass
# ✅ Создавай абстракции только для ТЕКУЩЕГО дублирования
# 2. DRY vs SOLID (Single Responsibility)
# ❌ Объединяем несвязанный код в одну функцию
def do_everything(user_id):
# Валидирует, загружает, сохраняет, отправляет email
# Выглядит как DRY, но нарушает SRP
pass
# ✅ Разные ответственности = разные функции
Заключение
DRY (Don't Repeat Yourself) — это не просто избежание дублирования кода. Это принцип, который учит:
- Искать общие паттерны и выносить их
- Хранить каждый факт в одном месте
- Делать код более maintainable
- Снижать вероятность ошибок при изменениях
- Упрощать тестирование и рефакторинг
Однако не переусложняй: создавай абстракции только для реального дублирования, а не для предполагаемого. DRY — это баланс между переиспользованием и читаемостью.