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)
Как исправить
- Выделять ответственности по признаку «что меняется вместе» (принцип единой ответственности).
- Разбивать на сервисы и модули, не трогая внешние интерфейсы.
- Сначала добавить характризационные тесты, затем извлекать методы и классы по одному.
- Не пытаться переписать за один присест — рефакторинг идёт итерациями.