25 окт. 2025 г.·4 мин. чтения

Разбейте файл utils итеративно с понятными границами модулей

Разбейте файл utils без рискованного переписывания — извлекайте небольшие модули шаг за шагом, задавайте ясные границы и делайте изменения безопасными для релиза.

Разбейте файл utils итеративно с понятными границами модулей

Почему один файл utils превращается в проблему

Один файл utils обычно начинается с благих намерений: положить мелкие хелперы в одно место. Потом наступают дедлайны, команда меняется, и всё, у чего нет очевидного места, попадает в utils. Со временем «разбить файл utils» перестаёт быть приятной уборкой и становится блокером для безопасных изменений.

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

Типичные симптомы появляются быстро:

  • Несвязанные обязанности перемешаны (форматирование, API-вызовы, авторизация, база данных, правки UI)
  • Круговые импорты, потому что многие части приложения зависят от одного файла
  • «Мелкие» функции с скрытыми побочными эффектами (чтение env, запись в хранилище, мутация глобального состояния)
  • Тесты писать сложно, потому что импорт одного хелпера подтягивает половину приложения
  • Логика, чувствительная к безопасности, разбросана по случайным помощникам (парсинг токенов, правила паролей, работа с секретами)

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

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

Как понять, что utils стал универсальной помойкой

Utils превращается в catch-all, когда он тихо становится зависимостью для всего остального. Самый большой предупреждающий знак — не число строк. Это то, что одна «помощная» правка может сломать половину приложения.

Следите за этими признаками:

  • Смешиваются несвязанные задачи: проверки авторизации, запросы к базе, форматирование и сетевые вызовы
  • Хелперы импортируют модули приложения (модели, роуты, контроллеры) вместо того, чтобы оставаться универсальными
  • Функции имеют побочные эффекты, хотя по названию кажутся безобидными
  • Вы редактируете utils, чтобы выпустить фичу, которая не имеет отношения к утилитам
  • Постоянное трение: конфликты слияния и сюрпризы «почему это сломало то?»

Размер — лишь симптом. Реальная проблема — связанность. Когда один файл знает про правила авторизации, схему данных и HTTP-клиент, скрытые зависимости накапливаются. Каждое маленькое изменение требует более широкого тестирования.

Чистые хелперы вроде capitalize(), clamp() или toSlug() вполне можно держать в утилитах. Если функция зависит от базы данных, состояния авторизации или секретов, это уже не утилита — это модуль, который ждёт выделения.

Выберите ясные границы модулей перед перемещением кода

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

Начните с категорий, которые люди уже понимают. Для многих приложений эти корзины покрывают большую часть беспорядка:

  • strings
  • dates
  • validation
  • api
  • auth

Самая важная граница — между чистыми хелперами и хелперами с побочными эффектами.

Чистые хелперы предсказуемы: одинаковый вход — одинаковый выход, без внешних эффектов. Их проще всего переносить в первую очередь, потому что их легко протестировать простыми примерами.

Хелперы с побочными эффектами затрагивают внешний мир: переменные окружения, localStorage, cookies, время, случайность, сетевые вызовы, базы данных и глобальное состояние приложения. Они также могут логировать, мутировать объекты или выбрасывать исключения таким образом, как на это рассчитывает другой код. Обращайтесь с ними как с более рискованными и держите сгруппированными по ответственности (например, не смешивайте auth и поведение API-клиента в одном модуле).

Держите нейминг последовательным, даже если он скучный. Решите заранее: папки или плоские файлы, и придерживайтесь простой конвенции, чтобы ревьюверам было легче заметить «misc»-модули до того, как они вырастут.

Быстрая карта содержимого (без чрезмерного планирования)

Перед тем как разделять файл utils, получите быстрое и честное представление о том, что в нём. Не нужна идеальная документация. Нужно достаточно ясности, чтобы перемещать код без догадок.

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

Лёгкая инвентаризация обычно достаточна:

  • Имя функции и однострочное описание
  • Где импортируется (несколько основных вызовов)
  • Побочные эффекты (env, storage, логирование, сеть)
  • Где ей место (date, auth, formatting, db)
  • Уровень риска: низкий (чистый), средний (зависит от конфигурации), высокий (состояние или безопасность)

Когда список перед глазами, найдите первую кластерную группу для извлечения: чистые функции, которые не читают глобальное состояние и не делают I/O. Перенос их первым даёт быстрые победы и чистый паттерн для повторения.

Пошагово: итеративное извлечение модулей с низким риском

Безопасно разбить utils
We diagnose circular imports and refactor toward clear modules without rewriting everything.

Самый безопасный способ разбить utils — делать прогресс маленькими срезами, которые легко ревьювать и откатывать.

Выберите одну тему (dates, formatting, validation, auth helpers) и используйте её как пилот. Цель — повторяемая рутина, а не идеальная архитектура.

Петля низкорискованного извлечения

  • Создайте новый файл модуля для выбранной темы.
  • Перенесите только 1–3 тесно связанных функций. Если что-то кажется наполовину связанным — оставьте на потом.
  • Сохраните стабильные экспорты, временно реэкспортируя перенесённые функции из оригинального utils.
  • Обновите импорты в небольшой части приложения (одна страница, одна фича, один сервис). Закоммитьте.
  • Удаляйте временные ре-экспорты только после того, как большинство импортов мигрировало.

Это держит каждый коммит маленьким: перенесли пару функций, обновили несколько импортов и остановились.

Конкретный пример

Если в utils.ts лежат formatMoney, parseCurrency, roundToCents, вместе с несвязанными хелперами вроде slugify, sleep, и fetchWithRetry, начните с денег. Создайте utils/money.ts, перенесите функции по деньгам и реэкспортируйте их из utils.ts, чтобы ничего не сломалось. Потом мигрируйте импорты в потоке оформления заказа в первую очередь, затем постепенно расширяйте.

Сохраняйте поведение прежним при рефакторинге

Самый большой риск — не в переносе кода, а в «пока я тут» правках, которые тихо меняют результаты. Относитесь к этому как к переноске коробок в другую комнату: маркируйте, переносите, ставьте. Не переставляйте мебель в процессе.

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

Форматирующие хелперы хорошо подходят для простых таблиц вход-выход. Выберите набор репрезентативных случаев, включая краевые: пустые значения, лишние пробелы и не‑ASCII символы.

Если у вас нет инфраструктуры тестирования, используйте быстрые проверки во время выполнения вместо догадок. Временные ассерты или короткие логи вокруг места вызова могут подтвердить, что вывод не изменился после перемещения. Удалите их, когда будете уверены.

Обычная ошибка: «улучшить» formatPrice(amount) во время перемещения. Если этот хелпер влияет на счета или письма, изменение округления или символа может привести к несоответствию сумм или путанице у клиентов. Заморозьте вывод, перенесите, а улучшения запланируйте отдельным явным изменением.

Аккуратно обращайтесь с побочными эффектами и чувствительными к безопасности хелперами

Самые рискованные части utils редко — строковые хелперы. Чаще всего это функции, которые трогают внешний мир: сетевые вызовы, хранилища, базы данных, время, генерация случайных ID и переменные окружения.

Держите чистые хелперы отдельно от побочных. Их смешивание приводит к багам: дублированные API-запросы, пропущенные заголовки или сохранение данных не туда.

Помещайте код с побочными эффектами в модули с очевидными именами: auth (чтение/запись токена, refresh, logout), настройка API-клиента (base URL, retries, headers), обёртки хранилища и доступ к env.

Чтобы сохранить стабильность вызывающего кода во время перемещений, вводите небольшие интерфейсы. Вместо повсеместного вызова localStorage.getItem('token') сделайте tokenStore с getToken() и setToken(). Позже вы сможете изменить способ хранения токена без правки половины приложения.

Относитесь к таким хелперам как к высоким рискам, даже если они кажутся маленькими:

  • Работа с токенами (парсинг JWT, refresh, проверки истечения)
  • Секреты (API‑ключи, сервисные токены, приватные URL)
  • Логика паролей (хеширование, сравнения, сбросы)
  • Санитизация ввода и построение запросов

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

Распространённые ловушки, превращающие рефактор в переписывание

Бесплатный AI-аудит кода
Send your repo and we will map risky utils, side effects, and hidden dependencies fast.

Рефакторы выходят из-под контроля, когда изменения становятся слишком большими, чтобы их охватить. Цель — маленькие извлечения, которым можно доверять.

Типичные ошибки:

  • Big-bang перемещения: перенос десятков хелперов сразу убивает возможность локализовать поломки
  • Смешивание уборки с изменением поведения: переименование и правки логики в одном коммите скрывают регрессии
  • Создание нового свалочного места: shared/ или common/ слишком рано часто становятся utils v2
  • Ломание импортов без плана миграции: лучше мост с ре-экспортами или алиасами
  • Скрытые зависимости: хелперы, читающие env или глобальные синглтоны, могут сломаться из‑за изменённого порядка инициализации

Простой пример: formatDate() кажется безобидной, но зависит от глобальной локали и env-переменной временной зоны. После переноса локальные тесты проходят, но в проде другая переменная окружения, и теперь чеки показывают неверную дату.

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

Быстрые проверки перед релизом каждого извлечения

Относитесь к каждому извлечению как к мини-релизу.

Убедитесь, что новый модуль выполняет одну задачу и имеет небольшую поверхность экспорта. Если вы не можете описать, что он делает в одном предложении — он всё ещё делает слишком много. Будьте намеренны в экспортах: многие хелперы были «публичными по случайности», пока жили в большом файле.

Короткий чеклист перед merge:

  • Ясная цель: имя и экспорты соответствуют одной доменной области
  • Преднамеренные экспорты: экспортируется только то, что реально нужно вызывающим
  • Нет круговых импортов
  • Секреты не подтянулись в клиентский бандл
  • Сборка проходит без предупреждений о дублированных путях или мёртвых импортов

Затем выполните небольшую smoke-проверку реального использования. Даже при юнит-тестах стоит проверить, что основной поток всё ещё работает (auth, основное создание/сохранение и одна страница, которая делает API-вызов и рендерит реальные данные).

Пример: разделение смешанного utils файла в реальном приложении

Восстановить чёткие границы
Replace spaghetti utils with clear modules your team can own and test.

Стартап имел один utils.ts, который начался небольшой, а вырос до 900 строк. В нём были хелперы для auth (хранение токена, проверки сессии), API (fetchWithAuth) и UI-форматирование (даты, валюта, display names). Баги появлялись в неожиданных местах, потому что всё импортировало всё.

Они продолжали фичевую работу, извлекая маленькие модули в безопасном порядке:

  • Сначала перенесли чистые форматирующие хелперы.
  • Затем извлекли обёртку API-клиента и оставили старые экспорты тонкими обёртками.
  • Разделили auth на отдельные модули для токенов и сессий, изолировав всё, что трогает хранилище.
  • Закончили финальной очисткой: удалили ре-экспорты, переименовали неясные функции и удалили реально неиспользуемые хелперы.

Что улучшается быстро — практично: меньше регрессий, яснее ответственность (изменения в auth не влияют на форматирование), проще проверять безопасность и быстрее вхождение новых разработчиков, потому что файлы соответствуют реальным концепциям приложения.

Следующие шаги: превратите план в устойчивый прогресс

Самый быстрый способ разбить utils — рассматривать это как серию маленьких, безопасных шагов.

Простой план на неделю:

  • День 1: выберите одну границу (date/time, strings, money, validation) и вынесите 3–5 чистых функций. Добавьте быстрые тесты.
  • День 2: вынесите ещё 3–5 в той же области.
  • День 3: извлеките один модуль с побочными эффектами (storage, cookies, fetch wrappers). Оставьте тонкие обёртки.
  • День 4: мигрируйте импорты в нескольких файлах. Добавьте простое правило, запрещающее новые импорты из старого catch-all.
  • День 5: уборка: переименуйте модули, добавьте короткие комментарии и задокументируйте, что ещё остаётся в старом файле.

Остановитесь, когда оставшийся файл utils — в основном совместимые обёртки и действительно общая «склейка», а не место, куда сваливается всё подряд.

Если вы унаследовали AI-сгенерированное приложение и utils кажется владельцем всего продукта, FixMyMess (fixmymess.ai) может помочь, проведя аудит кода и изолировав рискованные хелперы (auth, API client, storage, query builders), превратив разделение в безопасную, шипабельную последовательность вместо переписывания.

Часто задаваемые вопросы

Как понять, что файл utils действительно проблема, а не просто большой?

Если файл смешивает несвязанные задачи и небольшое изменение ломает много фич — это уже catch-all. Главный сигнал — связанность: хелперы импортируют модули приложения, имеют скрытые побочные эффекты или используются повсеместно.

Количество строк менее важно, чем то, от скольких частей приложения зависит этот файл.

Какие границы модулей выбрать перед началом перемещений кода?

Стремитесь к простым «одна задача» категориям, которые соответствуют тому, как люди мыслят о приложении: strings, dates, validation, api, auth. Держите чистые хелперы (без I/O и без глобального состояния) отдельно от хелперов с побочными эффектами (storage, env, network, database).

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

Что стоит вынести из utils в первую очередь?

Начните с чистых, низкорискованных хелперов — их легко тестировать и они не читают глобальное состояние. Также выбирайте хелперы с небольшим количеством мест вызова: их мигрировать быстрее.

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

Как рефакторить, не меняя случайно поведение?

Не меняйте поведение во время перемещения кода. Напишите небольшой тест (или быструю проверку во время выполнения), который зафиксирует текущий вывод, даже если он странный.

Относитесь к перемещению как к переноске коробок: сначала переместили, потом улучшили отдельным, явным изменением.

Какой самый безопасный пошаговый способ разделить файл utils?

Создайте новый модуль, перенесите 1–3 связанных функции и временно оставьте старые экспорты, реэкспортируя их из оригинального utils. Мигрируйте импорты в одной маленькой части приложения и закоммитьте.

После того как большинство мест вызова обновлены, удалите временные ре-экспорты и повторите с следующей группой.

Как избежать circular imports при извлечении модулей?

Круговые импорты обычно означают, что «utils» на самом деле делает доменную работу и зависит от внутренних частей приложения. Разделяйте по ответственности и держите зависимости в одном направлении.

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

Какие функции из utils наиболее чувствительны с точки зрения безопасности?

Всё, что касается токенов, секретов, паролей, env, хранения или построения запросов — высокорисково. Перенесите это в явно называемые модули (auth, env, storage, db), а не оставляйте разбросанным среди безобидных хелперов.

Также убедитесь, что код, читающий секреты, не попадёт по ошибке в клиентский бандл.

Стоит ли делать обёртки для localStorage, cookies и доступа к env?

Да, но делайте это осознанно. Создайте небольшой обёрток типа tokenStore.getToken() и tokenStore.setToken(), чтобы остальная часть приложения не вызывала localStorage или cookies напрямую.

Это делает будущие изменения безопаснее и уменьшает вероятность несовместимого обращения с токенами по всему коду.

Как мигрировать импорты, не сломав всё?

Предпочитайте короткий переходный период: поддерживайте старые импорты через ре-экспорты или тонкие обёртки, пока постепенно мигрируете места вызова. Делайте каждое извлечение достаточно маленьким, чтобы ревьювер мог быстро его понять.

Избегайте больших «big-bang» перемещений и коммитов, которые одновременно переименовывают, перемещают и меняют логику.

Когда стоит просить помощи с разбиением запутанного utils в AI-сгенерированном приложении?

Когда вы унаследовали AI-сгенерированный код, гигантский utils файл часто скрывает сломанные auth-потоки, открытые секреты и непредсказуемые побочные эффекты, из‑за которых любой маленький фикс становится рискованным. Если нужно быстро разделить без полного переписывания, FixMyMess может помочь: провести бесплатный аудит кода и изолировать рискованные хелперы (auth, API client, storage, query builders), превратив разбиение в безопасную, шептабельную последовательность вместо рефакторинга "всё и сразу".

Большинство проектов можно стабилизировать за 48–72 часа после идентификации и изоляции самых рискованных частей.