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

Как лучше всего оформить повторяющийся код?

2.0 Middle🔥 131 комментариев
#Python Core#Архитектура и паттерны

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

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

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

Как лучше всего оформить повторяющийся код?

Дублирование кода (копипаста) — один из главных врагов качественного кода. Существует много стратегий для борьбы с дублированием, и выбор зависит от контекста.

Принцип DRY (Don't Repeat Yourself)

Если код повторяется более 2 раз, его нужно выносить в отдельную функцию или компонент.

Стратегия 1: Выделение в функцию

Самый простой и часто используемый подход.

❌ Плохо — повторение

# Обработка разных типов данных
user_data = {"name": "Alice", "age": 30}
if not user_data.get("name"):
    raise ValueError("Name is required")
if len(user_data.get("name", "")) < 2:
    raise ValueError("Name must be at least 2 chars")

product_data = {"title": "Laptop"}
if not product_data.get("title"):
    raise ValueError("Title is required")
if len(product_data.get("title", "")) < 2:
    raise ValueError("Title must be at least 2 chars")

✅ Хорошо — функция для валидации

def validate_string(value: str, field_name: str, min_length: int = 2) -> None:
    """Валидирует строковое поле."""
    if not value:
        raise ValueError(f"{field_name} is required")
    if len(value) < min_length:
        raise ValueError(f"{field_name} must be at least {min_length} chars")

# Использование
user_data = {"name": "Alice"}
validate_string(user_data.get("name"), "Name")

product_data = {"title": "Laptop"}
validate_string(product_data.get("title"), "Title")

Стратегия 2: Использование decorators

Для повторяющейся логики вокруг функций (логирование, кеширование, обработка ошибок).

❌ Плохо — повторение обработки ошибок

def get_user(user_id: int):
    try:
        response = requests.get(f"https://api.example.com/users/{user_id}")
        return response.json()
    except requests.RequestException as e:
        logger.error(f"Failed to get user: {e}")
        return None

def get_product(product_id: int):
    try:
        response = requests.get(f"https://api.example.com/products/{product_id}")
        return response.json()
    except requests.RequestException as e:
        logger.error(f"Failed to get product: {e}")
        return None

def get_order(order_id: int):
    try:
        response = requests.get(f"https://api.example.com/orders/{order_id}")
        return response.json()
    except requests.RequestException as e:
        logger.error(f"Failed to get order: {e}")
        return None

✅ Хорошо — decorator для обработки ошибок

import functools
import logging

logger = logging.getLogger(__name__)

def handle_api_errors(func):
    """Обрабатывает ошибки API запросов."""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except requests.RequestException as e:
            logger.error(f"API error in {func.__name__}: {e}")
            return None
    return wrapper

@handle_api_errors
def get_user(user_id: int):
    response = requests.get(f"https://api.example.com/users/{user_id}")
    return response.json()

@handle_api_errors
def get_product(product_id: int):
    response = requests.get(f"https://api.example.com/products/{product_id}")
    return response.json()

@handle_api_errors
def get_order(order_id: int):
    response = requests.get(f"https://api.example.com/orders/{order_id}")
    return response.json()

Стратегия 3: Наследование (Inheritance)

Для классов с общей логикой.

❌ Плохо — дублирование в классах

class UserRepository:
    def __init__(self, db):
        self.db = db
    
    def find_all(self):
        try:
            return self.db.query("SELECT * FROM users")
        except Exception as e:
            logger.error(f"Database error: {e}")
            return []

class ProductRepository:
    def __init__(self, db):
        self.db = db
    
    def find_all(self):
        try:
            return self.db.query("SELECT * FROM products")
        except Exception as e:
            logger.error(f"Database error: {e}")
            return []

✅ Хорошо — наследование из базового класса

class BaseRepository:
    def __init__(self, db, table_name: str):
        self.db = db
        self.table_name = table_name
    
    def find_all(self):
        try:
            return self.db.query(f"SELECT * FROM {self.table_name}")
        except Exception as e:
            logger.error(f"Database error: {e}")
            return []

class UserRepository(BaseRepository):
    def __init__(self, db):
        super().__init__(db, "users")

class ProductRepository(BaseRepository):
    def __init__(self, db):
        super().__init__(db, "products")

Стратегия 4: Использование Composition (Композиция)

Более гибкий подход чем наследование.

class DatabaseHandler:
    def query(self, sql: str):
        try:
            return self.db.execute(sql)
        except Exception as e:
            logger.error(f"Database error: {e}")
            return []

class Repository:
    def __init__(self, db_handler: DatabaseHandler, table: str):
        self.db_handler = db_handler
        self.table = table
    
    def find_all(self):
        return self.db_handler.query(f"SELECT * FROM {self.table}")

# Использование
db_handler = DatabaseHandler()
users_repo = Repository(db_handler, "users")
products_repo = Repository(db_handler, "products")

Стратегия 5: Context Manager для повторяющейся логики

Для setup/cleanup операций.

❌ Плохо — повторение try-finally

def process_file1(filename):
    f = open(filename, 'r')
    try:
        data = f.read()
        # обработка
    finally:
        f.close()

def process_file2(filename):
    f = open(filename, 'r')
    try:
        data = f.read()
        # обработка
    finally:
        f.close()

✅ Хорошо — context manager

def process_file1(filename):
    with open(filename, 'r') as f:
        data = f.read()
        # обработка

def process_file2(filename):
    with open(filename, 'r') as f:
        data = f.read()
        # обработка

Стратегия 6: Template Method (паттерн проектирования)

Для алгоритмов с одинаковой структурой, но разными шагами.

from abc import ABC, abstractmethod

class DataProcessor(ABC):
    """Шаблон обработки данных."""
    
    def process(self, data):
        """Template Method — определяет структуру."""
        validated = self.validate(data)
        transformed = self.transform(validated)
        saved = self.save(transformed)
        return saved
    
    @abstractmethod
    def validate(self, data):
        pass
    
    @abstractmethod
    def transform(self, data):
        pass
    
    @abstractmethod
    def save(self, data):
        pass

class UserProcessor(DataProcessor):
    def validate(self, data):
        assert data.get("name"), "Name required"
        return data
    
    def transform(self, data):
        return {**data, "name": data["name"].upper()}
    
    def save(self, data):
        db.save("users", data)
        return data

class ProductProcessor(DataProcessor):
    def validate(self, data):
        assert data.get("title"), "Title required"
        return data
    
    def transform(self, data):
        return {**data, "title": data["title"].upper()}
    
    def save(self, data):
        db.save("products", data)
        return data

Стратегия 7: Утилиты (Utils)

Для небольших повторяющихся операций.

# utils/validators.py
def validate_email(email: str) -> bool:
    return "@" in email and "." in email

def validate_phone(phone: str) -> bool:
    return len(phone) >= 10 and phone.isdigit()

def normalize_phone(phone: str) -> str:
    return phone.replace("-", "").replace(" ", "")

# Использование
from utils.validators import validate_email, validate_phone, normalize_phone

user_email = "alice@example.com"
if validate_email(user_email):
    save_user(user_email)

Когда использовать каждый подход

СитуацияПодходПример
Просто повторение кодаФункцияВалидация, форматирование
Логика вокруг функцииDecoratorЛогирование, кеширование, обработка ошибок
Классы с общей логикойНаследование/КомпозицияRepository, Service
Открытие/закрытие ресурсовContext ManagerФайлы, БД подключения
Алгоритм с вариантамиTemplate MethodПроцессоры, парсеры
УтилитыМодуль utilsФорматирование, валидация

Чеклист борьбы с дублированием

Если код повторяется 2+ раза — выносите в функцию ✅ Параметризуйте различия — не копируйте и не меняйте ✅ Используйте правильный паттерн — функция, класс, decorator ✅ Тестируйте — повторно используемый код нужно протестировать ✅ Не переусложняйте — KISS (Keep It Simple, Stupid)

Добавлены абстракции должны упростить код, а не усложнить.

Как лучше всего оформить повторяющийся код? | PrepBro