В большом проекте может возникнуть задача выявления изменений для конечного пользователя на основе различий во внешнем коде приложения.
Разработчик Яндекс.
Маркета Никита Сидоров @nickchevr рассказал о том, как мы решили эту проблему с помощью библиотеки Diffector, о построении и анализе графа модуля в Node.js-приложениях, а также о поиске дефектов в коде перед его запуском.
«Сегодня я постараюсь быть с вами максимально откровенным.
Работаю в Яндекс.
Маркете чуть больше полутора лет. Столько же времени работаю в сети и начал замечать в себе изменения, их можете заметить и вы.
Моя средняя длина волос увеличилась, и начала появляться борода.
И знаете, сегодня я посмотрел на своих коллег: Сергей Бережной овощной , о Вове Гриненко Тадатута , и я понял, что это хороший критерий того, что я уже почти созрел как настоящий фронтенд-разработчик.
И подойдя к этому материнству, я решила поговорить с тобой о жизни, о той, в которой мы все участвуем.
В основном — о жизни до рантайма.
Сейчас я объясню, что это вообще такое.
А как насчет жизни? О жизни кода, конечно.
Код — это то, что мы с вами делаем.
Напомню, я решил быть с вами искренним, поэтому первый слайд был максимально простым.
Я принял прописную истину, первый этап – это принятие, понимаете, с этой аксиомой никто не поспорит.
И тогда я понял, что придется его модифицировать, но так, чтобы было понятно.
Пусть это будет своего рода принятие требований.
Любой код начинается с того, что вы смотрите на проблему и пытаетесь принять предъявляемые к вам требования.
После этого мы, естественно, приступаем к этапу написания — написанию нашего кода.
Затем мы покрываем его тестами и сами проверяем его эффективность.
После этого мы проверяем, работает ли всё наше приложение с нашим кодом.
После этого отдаем тестировщику - пусть проверит. Как вы думаете, что произойдет после этого? Позвольте мне напомнить вам, что жизнь существует до выполнения.
Как вы думаете, должно ли за этим следовать время выполнения? На деле получается вот так.
И это не ошибка в изложении.
Очень часто на каком-либо этапе проверок — а их может быть гораздо больше, чем я указал — у вас может возникнуть какой-то вызов goto для повторной записи.
Согласитесь, это может стать довольно большой проблемой.
Это может замедлить доставку какой-то фичи в производство и в принципе замедлить вас как разработчика, потому что тикет будет висеть на вас.
А потом все это проходит, проходит. Происходит еще несколько M раз N проверок, и только потом код попадает к пользователю в браузере.
Но это наша цель.
Наша цель — написать код, который действительно доступен пользователю и действительно работает на его пользу.
Сегодня поговорим о первой части.
О том, что было раньше, но не совсем об испытаниях.
Кстати, выглядит это примерно так.
Я взял нашу очередь в трекере, собрал свою и посчитал медиану.
Получается, что мои билеты в разработке гораздо меньше, чем на рассмотрении.
И как вы понимаете, чем дольше проверяется, тем выше шанс, что либо здесь в начале появится goto, либо в конце появится goto — а вам этого совсем не хочется.
А еще, если вы обратите внимание, здесь на слайде два слова — разработка (это то, чем мы, разработчики, занимаемся) и тестирование (это то, чем мы, как и тестировщики, занимаемся).
Поэтому проблема на самом деле актуальна и для тестировщиков.
Цель весьма прозаична.
Я вообще люблю говорить, что жизнь нужно упрощать: мы и так много работаем.
Цель выглядит примерно так, но согласитесь, она достаточно эфемерна, поэтому выделим некоторые основные критерии, от которых может зависеть цель.
Конечно, чем меньше кода, тем нам проще.
Чем быстрее мы проведем CI-проверку, тем быстрее поймем, правы мы или нет. То есть локально он вообще может работать вечно.
Скорость проверки — это уже касается непосредственно тестера.
Если наше приложение большое и нам нужно проверить его целиком, это огромный объем времени.
От всего этого зависит скорость выпуска.
В частности, мы не можем его выпустить, пока не пройдём все проверки и пока не поймём, что код — это именно то, что мы хотим.
Чтобы решить некоторые проблемы, о которых мы говорим, давайте проанализируем граф зависимостей модулей в нашем языке программирования.
И, собственно, опишем это.
Граф ориентирован: у него есть ребра с направлениями.
В узлах графа у нас будут находиться именно те модули того языка, о котором мы говорим.
Края — это особый тип соединения.
Выделим несколько видов общения.
Давайте рассмотрим тривиальный пример.
Вот есть файл А.
Вот в него импортируется что-то из файла Б, и это такая зависимость между узлами.
То же самое произойдет, если вы замените import на require. На самом деле здесь все не так просто.
Предлагаю, раз уж мы говорим о типе зависимости, рассматривать как минимум два типа — для ускорения вашего конвейера, для ускорения обхода графа.
Вам нужно смотреть не только зависимый модуль, но и зависимый.
Я предлагаю называть модуль A родительским, B — дочерним, и советую всегда хранить ссылки в виде двусвязного списка.
Это облегчит вам жизнь, я сообщу вам заранее.
Раз уж мы как-то описали граф, давайте договоримся, как мы будем его строить.
Есть два пути.
Или ваш любимый инструмент на вашем любимом языке программирования, использующий те же AST (абстрактные синтаксические деревья) или регулярные выражения.
Какая здесь прибыль? Дело в том, что здесь вы ни к кому не привязаны, но при этом вам придется все реализовывать самостоятельно.
Вам придется описать все виды связей всех тех вещей и технологий, которые вы используете, будь то отдельный сборщик CSS или что-то в этом роде.
Но твой полет – это, так сказать, полная свобода.
Кроме того, второй вариант, я его тоже немного буду продвигать, это вариант для большинства людей, у которых уже настроена система сборки.
Дело в том, что система сборки по умолчанию собирает граф в зависимости от дизайна.
Давайте рассмотрим одну из самых популярных систем сборки в Яндексе — webpack. Здесь я привел пример того, как можно собрать весь результат вебпака в отдельный файл, который потом можно скормить нашему или каким-то другим анализаторам.
Собирает он его с помощью AST, используя библиотеку acorn. Возможно, вы заметили это, когда на вас что-то упало.
Я заметил.
И в чем преимущества? Дело в том, что когда вы описывали свою систему сборки, вы абсолютно честно указали запись.
Это файлы, из которых разрабатываются ваши зависимости, отправные точки обхода.
Это хорошо, потому что вам не придется их записывать снова.
Помимо этого, вебпак и бабель, и все это, включая желудь, у вас до сих пор не поддерживается.
И поэтому всякие новые возможности языка, всякие баги и все остальное исправляются быстрее, чем если бы это делали вы, особенно если у вас не очень большая команда.
Да, даже если он большой, он все же не такой большой, как open source. На самом деле это и плюс и минус.
Это что-то вроде палки о двух концах (палка о двух концах).
Дело в том, что этот график строится при сборке.
Это в некотором роде хорошо, то есть мы можем собрать проект и сразу же переиспользовать результат сборки.
А что, если мы не хотим собирать проект, а просто хотим получить этот граф? И на самом деле есть еще один главный недостаток.
Если у вас подключены какие-то кастомные вещи, о подключениях мы поговорим много позже, то система сборки не позволит вам этого сделать.
Или вам придется интегрировать его, как плагин веб-пакета.
Давайте рассмотрим конкретный пример.
Я запустил команду в своем проекте, где всего три файла, и получил следующий результат. И я показываю только один ключ, который называется модули.
Мы просто говорим о графе зависимостей модулей, поэтому смотрим на модули, все логично.
Информации достаточно много, но вся она нам не нужна.
Оставим некоторые моменты и поговорим о них.
Допустим, мы рассматриваем первый модуль.
У него есть имя, у него есть причины.
Причины именно в связи, по сути с "зависимыми" модулями, получается у тех, кто импортирует этот модуль себе.
Это основные данные для построения графика.
Кроме того, обратите внимание на UsedExports и ProvideExports. О них мы поговорим чуть позже.
Но это тоже очень важные вещи.
А если описывать свое решение, то нужно рассказать о типах связей, которые существуют между модулями.
То есть у нас есть, конечно, своя система модулей внутри нашего языка: будь то модули cjs или модули esm. Кроме того, согласитесь, что у нас могут быть связи между файлами в файловой системе на уровне самой файловой системы.
Это какие-то фреймворки: собирается какой-то фреймворк, в зависимости от того, как расположены папки.
И такой тривиальный пример — если вы написали серверную часть Node, то довольно часто можно было увидеть такой популярный npm-пакет, как Config. Это позволяет вам довольно удобно определять ваши конфигурации.
Чтобы его использовать, вам нужно создать папку конфига, где у вас есть NODE_PATH, и указать несколько файлов JavaScript — просто чтобы вы могли представить там конфиг для разных сред. В качестве примера я создал папку и указал по умолчанию, разработку и производство.
И, по сути, весь конфиг работает примерно так.
То есть, когда вы пишете require('config'), он просто внутренне считывает требуемый модуль и берет имя модуля из переменной среды.
Как вы понимаете, было непонятно, что эти файлы как-то использовались, поскольку прямого импорта/требования не было; вебпак даже не распознает его.
Ссылка со слайда
Сегодня мы также говорили о внедрении зависимостей.Меня это не особо вдохновило, но я заглянул в одну из библиотек в поисках поддержки.
Это называется инверсификация JS. Как видите, он предоставляет довольно собственный синтаксис: lazyInject, nameProvider и все здесь.
И, согласитесь, непонятно, что за провайдер, какой модуль на самом деле сюда внедряется.
Но нам это нужно, и мы должны это понять.
То есть, опять же, система сборки ее не решит, и нам придется делать это самим.
Предположим, мы построили график, и я предлагаю вам начать с его где-нибудь сохранить.
Что это позволит нам сделать? Это позволит нам провести некий эвристический анализ, немного поиграть в Data Science и сделать это, ориентируясь на временной интервал.
В чем идея? Вот, действительно, наши данные.
Недавно мы внедрили нашу дизайн-систему в Яндекс.
Маркете и, среди прочего, внедрили библиотеку компонентов в составе этой дизайн-системы.
И тут вы можете видеть: мы считаем количество импортов, реактивный компонент из нашей библиотеки, общий компонент. И вы можете распределить его по каталогам.
В данном случае у нас такой немонорепозиторий, и поэтому у нас есть Platform.desktop, Platform.touch и src. Что мы можем подумать, увидев эти цифры? Мы можем предположить, что сенсорная команда, похоже, не увеличивает использование общих компонентов.
Это значит, что либо компоненты плохие для мобильных — плохо сделаны, либо сенсорная команда ленивая.
Но так ли это на самом деле?
Если мы посмотрим на более длительный период, на более длительный отрезок времени, что позволяет нам делать именно за счет сохранения графиков после каждого релиза, то мы поймем, что на самом деле для тача все ок, показатель там растет. Для src еще лучше, для десктопа оказывается нет.
Также был вопрос из зала о том, как объяснить важность менеджерам.
Вот общее количество импортов библиотек, также с разбивкой по времени.
Какие менеджеры не любят диаграммы? Вы можете построить такой график и увидеть, что использование библиотеки растёт, а значит, это как минимум полезная вещь.
Одна из моих любимых частей.
Я расскажу об этом довольно кратко.
Это поиск дефектов в графе.
Сегодня я хотел поговорить с вами о двух типах дефектов: циклическая зависимость модулей и какой-то неиспользуемый модуль, то есть проблема устранения мертвого кода.
Начнем с циклической зависимости.
Кажется, здесь все довольно просто.
Ориентированный граф у вас уже есть, осталось только найти там цикл.
Я объясню, почему я об этом говорю.
Дело в том, что раньше я писал в основном серверную часть на Node.js, и мы не использовали в принципе ни вебпак/бабел, ничего.
То есть запустили как есть.
И было требование.
Кто помнит разницу между импортом и требованием? Все правильно.
Если вы плохо написали код, а я это действительно написал, то вы на своем сервере можете узнать, что у вас модуль в какой-то циклической зависимости, только когда приходит какой-то запрос от пользователей, или срабатывает какое-то другое событие.
То есть это оказывается достаточно глобальная проблема.
Вы не сможете понять это до момента выполнения.
То есть с импортом все намного лучше, такой проблемы не будет.
Здесь вы просто берете любой понравившийся алгоритм.
Здесь я взял довольно простой алгоритм.
Мы должны найти вершину, имеющую только один тип ребра: входящее или исходящее.
Если такая вершина есть, удалим ее, удалим ребра и, по сути, продолжим этот процесс, найдем и докажем, что в этом графе был цикл длины пять.
Согласитесь, если посмотреть это в коде, то есть еще можно было найти цикл длиной в два-три, но это уже невозможно, а в нашем проекте у нас действительно был цикл длиной в семь, но не в производство.
О неиспользуемых модулях.
Это тоже довольно тривиальный алгоритм.
Мы должны выбрать в нашем графе связные компоненты и просто посмотреть, найти те компоненты, которые не включают в себя ни один из узлов входа.
В данном случае это и есть эта составляющая связности, обе вершины, получается, оба узла.
Здесь я назвал enter.js. На самом деле не важно, как он называется, это то, что вы имеете в виду в записи конфига сборки.
Но есть и другой подход. Если вы не собирали граф, а просто есть система сборки, то как это сделать дешевле всего? Давайте просто во время сборки отметим все файлы, которые включены в сборку.
Отметьте их и создайте множество.
После этого нам следует получить набор всех файлов, которые есть у вас в проекте и просто вычесть их.
То есть очень простые операции.
И я сейчас не просто рассказываю вам что-то теоретическое, я вдохновился, пришел в свой проект и сделал это.
И, внимание! Я даже не удалил node_modules. Я оставил это как точку роста для следующего обзора.
И, короче, я настолько вдохновился собой, что решил сделать этот слайд как-то по-другому, переоформить его.
Пусть это выглядит так, ведь это действительно круто! Хорошие цифры, представляете, как всем было хорошо? А потом меня загнали в такую степь, что я почувствовал себя дизайнером, и подумал, что это достижение, которое я хотел бы добавить в кадр.
И, как вы понимаете, я постоял, посмотрел и понял, что я скорее не дизайнер, а, действительно, веб-разработчик.
Но я не дурак.
Я взял этот кадр и добавил его на свой сайт в качестве SEO-амулета.
Можете воспользоваться, даже ссылка есть.
И чтобы вы не подумали, что я вас обманываю - мы сегодня откровенны - я действительно посмотрела отзывы.
Я думаю, мы можем им доверять.
Ладно, если честно, это выглядело примерно так.
Я увидел новую хайповую библиотекуthanos-js, взял ее и создал запрос на включение.
По секрету, у меня есть права администратора в нашем репозитории.
А я взял и убил мастера.
Как вам это нравится? Ладно, мы с вами откровенны, и на самом деле все выглядело так.
Если кто-то не знает,thanos-js — это библиотека, которая просто случайным образом удаляет 50% вашего кода.
На самом деле я там еще пользовался библиотекой, но библиотека называется по-другому.
Называется он дифектор, и сейчас мы о нем поговорим.
И здесь хотелось бы отметить, что пул-реквест довольно значительный, минус 44 тысячи строк кода, и можете себе представить — он прошел тестирование с первого раза.
То есть то, о чем я говорю, действительно может работать.
Дифектор.
На самом деле он занимается не только задачей удаления неиспользуемых модулей и поиска дефектов в графе, но и более важной задачей.
То, что я изначально заявлял, это помощь разработчику и тестировщику, сейчас об этом и поговорим.
И это работает примерно так.
Получаем список измененных файлов с помощью системы контроля версий.
График мы уже построили — его строит дифектор.
И для каждого такого измененного файла ищем путь к записи и отмечаем измененную запись.
И наша запись будет соотноситься со страницами приложения, которые увидит пользователь.
Но это довольно логично.
И что это нам дает? Для тестирования мы знаем, какие страницы в приложении изменились.
Мы можем сказать тестировщику, что они единственные, кого стоит тестировать.
Мы также можем указать нашему ci-job, который запускает автотесты, что следует тестировать только эти страницы.
А для разработчиков все гораздо проще, потому что теперь вам не пишут тестировщики и не спрашивают: «Что тестироватьЭ»
Давайте посмотрим на примере того, как работает дифектор.
Здесь у нас есть некий каталогpages.desktop/*.
Он просто содержит некий список самих страниц.
И страницы тоже описываются несколькими файлами.
Контроллер — это серверная часть страницы.
Просмотр — это своего рода реагирующая часть.
И, кстати, это из другой системы сборки.
У нас есть не только вебпак, но и ENB.
И я внес некоторые изменения в проект, в пустой файл, структуру которого вы видели.
Вот что мне дает диффектор.
Я только что запустил его, diffector — это приложение командной строки.
Запустил, мне пишет, что изменилась одна страница, которая называется BindBonusPage.
Я также могу запустить его в подробном режиме, посмотреть более подробный отчет и действительно убедиться, что он работает, по крайней мере, в таком простом случае.
Как мы видим, в нашей BindBonusPage изменились индексный файл и контроллер.
Но давайте посмотрим, что произойдет, если мы изменим что-то еще.
Я изменил кое-что еще.
И дифектор сказал мне, что изменилось девять страниц.
И это меня уже не радует, как бы он мне ни помогал.
Давайте посмотрим, почему? Вот причины, по которым эта страница считалась измененной.
И как мы видим, здесь то же самое.
Это какой-то текстовый компонент из uikit.
И давайте посмотрим разницу.
Я взял и просто поменял типы комментариев.
Но согласитесь, что в данном случае диффузор сработал некорректно.
На самом деле не стоит запускать тесты на всех этих девяти страницах, которые были включены только из-за текста и добавлять их в регрессию.
И это действительно проблема.
Если у нас есть файлы, которые часто используются, согласно проекту, любое изменение в файле вызовет изменение всех ваших записей, а это значит, что все страницы вашего приложения попадут в test-scope, и тогда эффективность возрастет. просто ноль.
Вот как это должно быть решено.
Тришейкинг.
Надеюсь, большинству людей знаком этот термин и вообще, из чего он состоит.
Прежде всего, еще раз скажу о проблеме.
У нас есть какой-то файл, он часто используется.
Как пример i18n, который сделан на коленке, где просто хранятся ключи.
Изменив его, вы существенно изменили все приложение в плане коммуникаций в графе.
Но на самом деле вы лишь изменили места, где используется определённый ключ.
И как нам сюда попасть? Если раньше наш модуль был одним файлом в файловой системе, то теперь у нас есть модуль, это действительно экспорты, которые находятся в нашем файле.
То есть что-то вроде этого.
Если раньше было так, то теперь мы делим файл B на экспорты, и получается, что у экспорта-2 нет даже никакого края.
То есть он нам даже не нужен.
Точно так же работает тришейкинг и в вашей системе сборки, если esm.
Но здесь не все так радужно.
Давайте посмотрим на следующий код.
Представьте себе, что если мы изменим значение здесь, мы на самом деле изменим не только эту константу.
Мы также изменим метод статического класса, а также метод класса, вызывающий метод статического класса.
То есть у нас есть вторая часть задачи, где нам нужно разрешить зависимости внутри самого файла.
Да, можно попробовать сделать это с помощью AST, но я вам скажу, что специально для такого случая будет около 250 строк кода, и не факт, что все это заработает. И, на самом деле, могут быть зависимости, инвертированные по-другому, и все такое, так что это довольно сложная задача.
Также представьте, что у вас есть некий GlobalContext и некая функция, которая его меняет. Как мы вообще можем понять, что когда мы меняем функцию изменения, что на самом деле изменилось? В этом модуле или как-то при изменении GlobalContext. Но это так тяжело.
Поэтому мы не реализовали полноценный тришейкинг.
Кстати, это называется побочными эффектами.
Возможно, вы видели этот флажок при настройке веб-пакета; вы можете указать это там.
То есть если вы поставите модуль в webpacksideEffects:true, то проблем у вас не будет. Но это будет гарантировано.
Чтобы решить эту проблему, мы решили немного подзадорить наших разработчиков и ввели интерактивный режим.
Я объясню что и почему.
Вместо простого запуска дифектора теперь его можно запускать как полноценное консольное приложение.
Здесь, как мы видим, я запустил его в интерактивном режиме.
И вот он показал мне изменения — все, что было сделано в моем коде.
И те страницы, которых он затронул.
Как мы видим, у меня здесь есть повестка дня, то есть diff, расширить, логировать, и помимо этого мы видим здесь то изменение, о котором я вам уже говорил, изменение текста.
Вот, напомню.
Мы можем посмотреть на него с помощью кнопки D, увидеть разницу.
И сам разработчик может понять, что ему, например, не нужно включать текст. Изменения текстового компонента фактически не повлияют на контент для пользователя.
Так что мы можем смело удалить его из этого списка изменений, уменьшив количество страниц, которые нам нужно протестировать.
То есть мы взяли и удалили восемь страниц.
Здесь Теги: #JavaScript #интерфейсы #Промышленное программирование #технический долг #ast #webpack #графы #внедрение зависимостей #Системы сборки #время выполнения
-
Мировая Атомная Энергетика В 2018 Году
19 Oct, 24 -
Как Запустить Service Desk «Из Коробки»?
19 Oct, 24 -
Конфликт Прав На Доменное Имя
19 Oct, 24 -
Трекмания Навсегда!
19 Oct, 24