Однажды на работе я решил добавить новую служебную функцию в служебный файл функции.

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

Такое поведение вызывало основную проблему: наша система не была детерминированной.

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

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

Проблема

Проще говоря, циклическая зависимость - это когда файлу A нужно что-то из файла B для запуска, в то время как файлу B нужно что-то из файла A.

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

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

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

Я запустил набор тестов, и все они прошли.

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

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

Решение

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

После его установки я с удивлением обнаружил, что в нашем клиентском приложении обнаружено 238 нарушений.

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

Процесс

Сообщения плагина описывают цепочки в текстовом формате, например `file-a.js -› file-b.js - ›file-a.js`. Я начал просматривать этот список из 238 цепочек, но было сложно понять, с чего начать, поскольку порядок в списке был бесполезным. В настраиваемой конфигурации я добавил код для агрегирования подсчетов папки функций, в которой началась цепочка.

В итоге это стало отличным способом рекламировать и заручиться помощью других функциональных команд.

Команда, которой принадлежала функция, помогла с цепочками в своей функции. Исправление одного иногда приводило к разрешению нескольких цепочек, что обнадеживает. Все это хорошо работало для простых цепочек зависимостей, но не все из них были такими простыми. Эта настраиваемая конфигурация доступна в Github Gist.

Некоторые цепочки растягивали несколько файлов, самый длинный из которых занимал 32 файла.

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

Визуализация проблемы

Чтобы разгрузить умственные усилия, я решил сделать визуализацию, чтобы лучше понять проблему и, надеюсь, определить, где сделать оптимальное исправление. Я нашел начальный код для визуализации графика D3, который затем отредактировал для нашего случая использования.

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

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

Изначально каждый узел файла выглядел одинаково, поэтому я назначил каждому цвет в зависимости от папки с его функциями.

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

Ключевые темы определены

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

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

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

Польза для команды

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

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

Хотя трудно оценить будущие преимущества этой работы, я знаю, что существующие ошибки были исправлены, часы отладки сэкономлены, и мы больше доверяем нашей платформе и инструментам разработки.

Первоначально опубликовано на https://www.cbinsights.com.