Блог Михаила | Python | Разработка | Best Practices

"Всем привет! Меня зовут Михаил, я веду Telegram-канал «Python Шпильки», где делюсь изящными приемами программирования. Сегодня хочу показать один из самых полезных паттернов..."

Введение

Представьте: перед вами 200 строк кода, сплошь состоящих из if-elif-else конструкций. Каждый новый condition — еще одна ветка, еще сложнее читать, еще страшнее поддерживать. Знакомо?

В этой статье я покажу изящный прием, который превратит ваши многоэтажные условия в плоский, легко расширяемый и тестируемый код. Всего за 5 минут вы научитесь писать код, который коллеги будут показывать как пример для подражания.

Проблема: когда условия захватывают код

Допустим, мы пишем обработчик заказов в интернет-магазине:

python

def handle_order(status, order_data):
    if status == "new":
        validate_order(order_data)
        process_payment(order_data)
        send_confirmation_email(order_data)
        
    elif status == "processing":
        check_inventory(order_data)
        assign_to_warehouse(order_data)
        update_tracking(order_data)
        
    elif status == "shipped":
        send_tracking_email(order_data)
        update_analytics(order_data)
        
    elif status == "cancelled":
        process_refund(order_data)
        notify_customer_service(order_data)
        update_inventory(order_data)
        
    elif status == "refunded":
        close_financial_transactions(order_data)
        archive_order(order_data)
        
    # ... и так еще 10-15 статусов

Что здесь не так:

  • ❌ Код растет вертикально с каждым новым статусом

  • ❌ Сложно тестировать отдельные сценарии

  • ❌ При изменении одного статуса нужно перелопачивать всю функцию

  • ❌ Новый разработчик будет разбираться часами

Решение: словарь диспетчеризации функций

А теперь посмотрите на эту же логику, переписанную с помощью паттерна "Словарь диспетчеризации":

python

class OrderProcessor:
    def __init__(self):
        self.handlers = {
            "new": self._handle_new,
            "processing": self._handle_processing,
            "shipped": self._handle_shipped,
            "cancelled": self._handle_cancelled,
            "refunded": self._handle_refunded,
        }
    
    def process_order(self, status, order_data):
        handler = self.handlers.get(status, self._handle_unknown)
        return handler(order_data)
    
    def _handle_new(self, order_data):
        validate_order(order_data)
        process_payment(order_data)
        return send_confirmation_email(order_data)
    
    def _handle_processing(self, order_data):
        check_inventory(order_data)
        assign_to_warehouse(order_data)
        return update_tracking(order_data)
    
    def _handle_shipped(self, order_data):
        send_tracking_email(order_data)
        return update_analytics(order_data)
    
    def _handle_cancelled(self, order_data):
        process_refund(order_data)
        notify_customer_service(order_data)
        return update_inventory(order_data)
    
    def _handle_refunded(self, order_data):
        close_financial_transactions(order_data)
        return archive_order(order_data)
    
    def _handle_unknown(self, order_data):
        logger.warning(f"Unknown order status: {order_data.get('status')}")
        return {"error": "Unknown status"}

# Использование:
processor = OrderProcessor()
result = processor.process_order("new", order_data)

Почему это решение элегантнее?

🎯 Принцип единственной ответственности

Каждый метод отвечает только за один статус. Изменили логику отмены? Правите только handlecancelled.

🧪 Простота тестирования

python

def test_cancelled_order():
    processor = OrderProcessor()
    test_data = {"id": 123, "amount": 100}
    
    # Тестируем только обработку отмены
    result = processor._handle_cancelled(test_data)
    
    assert result["refund_processed"] == True
    mock_notify.assert_called_once()

🚀 Легкое расширение

Добавляем новый статус без изменения существующей логики:

python

def add_preorder_status(self):
    self.handlers["preorder"] = self._handle_preorder

def _handle_preorder(self, order_data):
    reserve_inventory(order_data)
    return send_preorder_confirmation(order_data)

📊 Визуальная чистота

Теперь архитектура вашего обработчика видна как на ладони:

python

# ВСЯ ЛОГИКА ПРЕДСТАВЛЕНА В ОДНОЙ СТРОКЕ
handlers = {
    "new": self._handle_new,
    "processing": self._handle_processing,
    "shipped": self._handle_shipped,
    # ...
}

Продвинутые возможности

Динамическая регистрация обработчиков

python

def register_handler(self, status, handler):
    self.handlers[status] = handler

# Где-то в другом модуле:
processor.register_handler("preorder", custom_preorder_handler)

Декоратор для автоматической регистрации

python

def register(status):
    def decorator(method):
        method._handler_status = status
        return method
    return decorator

class OrderProcessor:
    def __init__(self):
        self.handlers = {}
        self._auto_register_handlers()
    
    def _auto_register_handlers(self):
        for attr_name in dir(self):
            attr = getattr(self, attr_name)
            if hasattr(attr, '_handler_status'):
                self.handlers[attr._handler_status] = attr
    
    @register("new")
    def handle_new_order(self, order_data):
        # ...

Когда использовать этот паттерн

✅ Идеально подходит для:

  • Обработчиков HTTP-запросов (API роутинг)

  • State-машин и обработки статусов

  • Парсеров различных форматов данных

  • CLI-утилит с множеством команд

❌ Возможно, не стоит использовать:

  • Когда условий всего 2-3 (оверкиллинг)

  • Когда логика слишком простая для вынесения в отдельные функции

  • Когда производительность критична (есть минимальные накладные расходы)

Бенчмарк: если сомневаетесь в производительности

python

import timeit

# if-else подход
def if_else_approach(status):
    if status == "new": return 1
    elif status == "processing": return 2
    # ... 10 условий
    else: return 0

# Словарный подход  
def dict_approach(status):
    return handlers.get(status, lambda: 0)()

handlers = {"new": lambda: 1, "processing": lambda: 2}  # ... 10 условий

# Результаты для 100000 вызовов:
# if-else: 0.015 секунд
# dict: 0.012 секунд

Вывод: Словарный подход часто даже быстрее за счет хэш-таблиц!

Заключение

Паттерн "Словарь диспетчеризации" — это не просто синтаксический сахар. Это:

  • 🏗 Архитектурное решение для сложной логики

  • 🧹 Инструмент рефакторинга для борьбы с code smell

  • 🎨 Демонстрация мастерства в Python

Следующие шаги:

  1. Найдите в своем коде самый страшный if-elif-else блок

  2. Попробуйте переписать его с помощью словаря функций

  3. Поделитесь результатами в комментариях!

А в следующей статье разберем, как комбинировать этот подход с декораторами для создания настоящих произведений искусства в коде.


Теги: PythonРефакторингПаттерны проектированияBest PracticesКод качество

Понравилась статья? Подписывайтесь на мой Telegram-канал Python Шпильки — там регулярно выходят такие же изящные решения для повседневных задач разработчика.