← Назад к обзору

Blob (God Object)

Это жирнющая штуковина, которая делает всё. Обычно это класс, который разросся сначала до уровня универсального решателя всего-всего внутри проекта, а потом чуть ли не стал операционной системой.

Появиться он может из-за неправильных решений в архитектуре или просто инкапсуляции всего подряд в страшной спешке. Либо из-за последовательного улучшения какого-то класса несколькими поколениями разработчиков, которым просто вбить костыль и пойти дальше.

Эта штука перестаёт быть в полной мере объектно-ориентированной и возвращает нас в мрачные времена расцвета программирования. На практике такие конструкции я встречал в банковском легаси. Всё, что было примерно после нулевых, уже проектировали с головой — независимо от того, объектное или функциональное там программирование. Даже старые монолиты могут быть разделены на функции так, что получается модульная структура, легко переживающая изменения (она только релизится вместе, а разрабатывается независимо).

Симптомы

  • Класс огромного размера и десятка ответственностей: бизнес-логика, валидация, доступ к данным, логирование, форматирование.
  • Модуль «знает про всех»: вызывает сервисы, дёргает базу, строит отчёты.
  • Почти каждый метод принимает и возвращает «всё на свете».
  • Любое изменение требует пройти через этот класс.

Почему возникает

Опять же очень часто блоб выступает просто первым симптомом ещё каких-то проблем. Я видел смелых людей, пытавшихся такое рефакторить (да и сам участвовал пару раз). Так вот, за этим нагромождением обычно бывают сразу десятки частных случаев плохих практик.

Думаю, что не сильно ошибусь, если скажу, что у каждого банка из топ-10 в России есть система, которой больше 15 лет и в которой есть штуковина размером с фреймворк для бизнес-логики, вокруг которой аккуратно расставлены обработчики и разные внешние интерфейсы. Сама же система не поддаётся членению и не может быть переписана в ближайшие годы.

Пример

«Плохой» вариант: один класс, который делает всё:

class OrderService:
    def create_order(self, cart):
        ...
    def apply_discount(self, order, code):
        ...
    def calculate_total(self, order):
        ...
    def save_order(self, order):           # доступ к данным
        ...
    def build_invoice_pdf(self, order):    # отчёты
        ...
    def send_email(self, order):           # уведомления
        ...
    def validate_customer(self, c):        # бизнес-правила
        ...

«Хороший» вариант: класс делегирует ответственности сервисам:

class CreateOrderHandler:      # оркестрация
    def __init__(self, pricing, storage, notifier):
        ...
    def handle(self, cart):
        order = Order(cart.items)
        pricing.apply_discounts(order)
        storage.save(order)
        notifier.order_created(order)

Как исправить

  • Выделять ответственности по признаку «что меняется вместе» (принцип единой ответственности).
  • Разбивать на сервисы и модули, не трогая внешние интерфейсы.
  • Сначала добавить характризационные тесты, затем извлекать методы и классы по одному.
  • Не пытаться переписать за один присест — рефакторинг идёт итерациями.