Прекратите называть ваши Python модули «utils»

Spread the love

Перевод статьи: Stop naming your python modules “utils”

Представьте себе следующую типичную ситуацию: разработчик, который либо добавляет новый код, либо реорганизует существующий, работая с каким нибудь новым классом или функцией. Ему нужно решать где разместит этот код, но он функционально не подходит к уже существующим модулям. И что же обычно делает разработчик в этой ситуации? Он создает новый модуль — utils.py.

Спонсор поста Блог о программирование — «Старая полка»:

В блоге вы найдете:
— Небольшой сборник софта за многие годы. Программы для Windows, DOS и браузера.
— Компьютерные советы и небольшие программы с пояснениями.
— Ссылки на софт.

Пример статьи:
Установка сертификата от Let’s Encrypt

Почему utils это ужасное имя?

Возможно, utils — одно из худших названий модуля, потому что оно очень размыто и неточно. Такое имя не говорит, какова цель кода. Напротив, модуль utils может содержать почти все что угодно. Присваивая имя utils модулю, разработчик устанавливает идеальные условия для создания несвязного кода. Поскольку имя модуля не говорит членам команды, подходит ли им то что внутри или нет, вполне вероятно, что в конечном итоге там появится много несвязанный кода. Подробнее об этом чуть позже.

Синонимы utils, таких как helpers, commons и т. д., так же являются плохим выбором по тем же причинам.

Почему люди делают это?

Причина 1 – это всего лишь одна функция

Изначально да — это может быть только одна функция. Одна функция в плохо названном модуле не так ли?

Как и в теории с разбитыми окнами (broken windows theory), одно из проявлений плохого поведения побуждает к большему. Одна функция или класс в utils — это небольшая проблема. Следовательно, это может быть реорганизовано, когда будет время. Как только модуль utils начнет расти, потребуется больше усилий для его разделения. И сюрприз, сюрприз, никто не захочет этого делать.

Насколько это плохо? Однажды в одном репозитории Python я увидел несколько модулей utils.py. Он содержал 13 различных функций и один служебный класс. Что делали эти функции? Все, от проверки и нормализации данных, до сохранения в базе данных, и отправки HTTP-запросов, а так же форматирование текущей даты и времени в соответствии с параметром формата (Да, это были отдельные, не к чему не привязанные функции).

def send_post_request(url, data, logger):
    ...
 
def save_details(source_obj, override_data_from_source_obj: dict = None):
    ...
 
def normalize_address(address: str) -> str:

Так выглядит ад программирования. utils.py быстро становится водоворотом для всего кода, который не подходит в другие места. Это плавно приводит нас к причине № 2 …

Причина 2 – Нет другого места, чтобы расположить этот код

Действительно, может не быть места для нового класса / функции. Это нормальная реакция при создание нового кода. Однако программист должен приложить больше усилий, думая об имени модуля. Как мы знаем, самая легкая дорога с utils — это скользкий путь.

Мы можем добиться большего, назвав модуль по назначению функций, находящихся внутри. Если они будут создавать другие объекты, назовем его factories.py. Если их роль — валидация — используйте validators.py. Может быть, нам нужно несколько функций, которые работают с телефонными номерами? Посмотрите, не могут ли они быть обычным классом PhoneNumber с сохранением состояния и просто поместите его в отдельный файл — phone_number.py.

Особый случай — функции с бизнес-логикой. Для этого существует множество методов, некоторые из которых более сложны, чем другие, но давайте рассмотрим простой случай. Предположим, у нас есть веб-приложение Django + DRF, которое содержит бизнес-логику в сериализаторах. Мы медленно переносим наш API на версию 2, и нам нужно поместить бизнес-логику, извлеченную из сериализатора V1, в другое место, чтобы сериализатор V2 мог использовать это повторно. НЕ ОСТАВЛЯЙТЕ ЭТО В utils.py. Попробуйте поместить бизнес-логику в модуль services.py. Служба имен происходит из службы приложений — единственное, что приложение делает для клиентов. Если это, например, бронирование рейса, сервис может называться flight_booking_service и оно может:

  • авторизовать оплату по платежной карте клиента
  • забронировать рейс через стороннего провайдера
  • отправить электронное письмо (или использовать для этого задачу Celery)

Причина 3 Мне нужно место для общих файлов

Допустим, вы создаете распределенное приложение, и есть фрагменты кода, которые необходимо повторно использовать в большинстве или во всех микросервисах. Естественная реакция — собрать их вместе где-нибудь, например, в отдельный репозиторий, который будет установлен как пакет. Но, пожалуйста, не называйте это {company_name}-utils. Я слышал о случае такого хранилища, но, к счастью для его сопровождающих, он был не таким уж большим. Он содержал код, отвечающий за:

  • обработку паролей, используя публичные облачные сервисы
  • регистрацию конфигураций, которые использовали определенные публичные сервисы

Как я уже сказал, это не так уж и плохо, но было бы лучше, если бы они были более конкретными с именем, например, cloud-toolkit, или были бы разбиты на два отдельных репозитория / пакета, потому что, честно говоря, может существовать микросервис, который используют только одну из функций.

Причина 4 – Но Django делает это

Да, в Django есть несколько пакетов utils. Позор им за использование названия utils. Однако обратите внимание, что, по крайней мере, некоторые из них могут быть отделены от фреймворка и они связаны как необязательные зависимости. Кроме того, по крайней мере, они сгруппированы в связные подпакеты — например, django.utils.timezone или django.utils.translation.

Если вы не пишете фреймворк, держитесь подальше от utils. 😉

Все utils плохие?

Не совсем. В конце концов, может понадобиться пара вспомогательных функций. В этом случае организуйте такой код в модулях, названных по теме — например, datetime, phone_numbers и т. д. Такие функции должны быть чистыми (с точки зрения функционального программирования).

Чистые функции — не имеют дополнительного функционала, то есть они не меняют состояние программы. При одинаковых входных параметрах чистая функция всегда будет выдавать один и тот же результат.

https://stackabuse.com/functional-programming-in-python/

Заключение

Не используйте utils как имя для вашего модуля Python и не помещайте его в имя класса. Постарайтесь более точно определить роль кода — например, validators, services или factories. Если критерий роли не помогают, и вы действительно имеете дело с утилитой, попробуйте сгруппировать код по его теме.

Модуль utils опасны, потому что со временем они создает беспорядок. Каждый, кто добавляет что-то, что никуда не подходит, с радостью добавит это в модуль utils, увеличив его несвязность. Беспорядок будет расти со временем, становясь все большим и большим бременем для работы.

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

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

Была ли вам полезна эта статья?
[10 / 3.1]

Spread the love
Подписаться
Уведомление о
guest
2 Комментарий
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Анонимно
Анонимно
1 год назад

А ты не думал писать буквы более связанно, чтобы получались слова, а затем предложения? Как попрактикуешься — напиши об этом статью

default.jpg
Андрей
Андрей
1 год назад

Что за бред я прочитал…