Pull to refresh

Оптимизация или как не выстрелить себе в ногу

Reading time8 min
Views4.3K

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


Первым делом разберёмся, что такое оптимизация в целом, и, что такое оптимизация в JS. Итак, оптимизация — это улучшение чего-либо по какой-либо количественной характеристике. У JS для себя выделил четыре количественные характеристики:


Объем кода — принято считать, что чем меньше написано строк кода, тем он производительнее и лучше. Мое мнение кардинально отличается, поскольку, написав одну строчку кода можно создать такую утечку памяти или вечный цикл, что браузер просто умрет.


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


Скорость сборки — ни для кого не секрет, что сейчас практически ни один из проектов не обходится без сборщиков вроде Webpack или Gulp, поэтому данная характеристика отображает правильность настройки сборщика проекта. Поверьте, когда сервер чуть умнее кофемолки это становится важным.


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


Объем кода:

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

Быстродействие:


  • Использование механизма кэширования браузера;
  • Оптимизация кода в расчёте на те среды, в которых он будет выполняться;
  • Наличие утечек памяти;
  • Использование веб-воркеров;
  • Использование ссылок на элементы DOM –дерева;
  • Использование глобальных переменных;
  • Наличие рекурсивных вызовов;
  • Упрощение математических вычислений.

Скорость сборки:


  • Количество внешних зависимостей;
  • Преобразования кода. Имеется в виду количество чанков и их размер, преобразования css, склейка файлов, оптимизация графики и много чего еще.

Переиспользование кода:


  • Количество компонентов;
  • Повторяемость компонентов;
  • Гибкость и кастомизация.

Как говорил в предыдущих статьях, чтобы что-то изменить необходимо определить отправную точку и разобраться на сколько все плохо. С чего начать столь объемный процесс? Начинайте с самого простого: ускорьте время сборки и выпиливания лишнего из проекта. Вы спросите почему стоит начать именно с этого? Из-за того, что они зависят друг от друга. Уменьшение объемов кода приведет к увеличению скорости сборки билдов, а, следовательно, увеличению вашей производительности.


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


Увеличить скорость сборки билда поможет:


  • Использование современных сборщиков. Технологии не стоят на месте, и если у вас первый webpack, то при переезде на четвертый вы увидите приятный прирост уже ничего не делая;
  • Избавление от всех мертвых зависимостей. Периодически разработчики, в попытках нащупать истину на дне банки с серной кислотой, забывают прибрать за собой после собственных экспериментов.Мой коллега, как то спросил: «Разве зависимости, которые прописаны в package.json, но не импортирующиеся нигде в коде, попадают в сбилженный бандл?». Да, в саму сборку они не будут включены, но пакет будет выкачен. Вопрос, зачем?
  • Разделить сборку на несколько профилей в зависимости от потребностей. Минимально на два: prod и dev. Показательный пример: обфускация кода. На prod это обязательно к исполнению, так как меньший вес = быстрой загрузке, а вот на dev обфускация только мешает и тратит время сборки на ненужные манипуляции;
  • Распараллеливания отдельных этапов сборки;
  • Использование npm-клиентов, которые умеют кешировать.

Ускорение ребилда и «холодного» билда потребуют выпилить лишние комментарии и мертвые куски кода. Однако, что делать если у вас огромный проект и самому его проинспектировать не представляется возможным? В таких случаях приходят на помощь код анализаторы.


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


Кардинальное отличие SonarQube от ESlint/TSLint/Prettier и иже с ними, в том, что он проверяет именно качество кода, вычленяет дубляж, сложность вычисления, а также выдаёт рекомендации по необходимым изменениям. Аналоги же просто проверяют код на ошибки, синтаксис и форматирование.


На практике сталкивался с codacy, неплохой сервис с бесплатной и платной подпиской. Он будет полезен, если надо что-то проверить на стороне, при этом не разворачивая у себя этот ‘комбайн’. Имеет интуитивно понятный интерфейс, подробное указание что не так с кодом и многое другое.


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


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


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


Жизненное правило: если работает — не трогай, не должно руководить вами в этом процессе. Первое правило в IT: сделай backup, потом сам себе скажешь спасибо. На фронте перед началом каких-либо изменений, сделайте тесты, чтобы не потерять функциональность в будущем. Затем спросите себя — как определять время загрузки и утечки памяти?


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


Мы смогли определить проблемы, теперь давайте их решать!


Для начала уменьшим время загрузки, используя механизм кэширования браузера. Браузер может все закэшировать и в дальнейшем предоставлять пользователю данные из кэша. Localstorage и sessionstorage никто у вас не отнимал. Они позволяют хранить часть данных, содействующие в ускорении SPA при последующих загрузках и сократить лишние запросы к серверу.


Считается необходимым оптимизировать код в расчёте на те среды, в которых он будет выполняться, но как показывает практика, это съедает достаточно много времени и сил, при этом не приносит ощутимого прироста. Предлагаю рассматривать это только как рекомендацию.
Естественно желательно устранить все утечки памяти. Заострять на этом внимание не стану, как их устранить думаю все знают, а если нет, то just google it.


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


  1. Выделенные воркеры (Dedicated Workers) — Экземпляры выделенных веб-воркеров создаются главным процессом. Обмениваться данными с ними может только сам процесс.
  2. Разделяемые воркеры (Shared Workers) — Доступ к разделяемому воркеру может получить любой процесс, имеющий тот же источник, что и воркер (например, разные вкладки браузера, iframe, и другие разделяемый воркеры).
  3. Сервис-воркеры (Service Workers) — это воркеры, управляемые событиями, зарегистрированные с использованием источника их происхождения и пути. Они могут контролировать веб-страницу, с которой связаны, перехватывая и модифицируя команды навигации и запросы ресурсов, и выполняя кэширование данных, которым можно очень точно управлять. Всё это даёт нам отличные средства управления поведением приложения в определённой ситуации (например, когда сеть недоступна).

Как с ними работать можно без проблем найти на просторах интернета.


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


Первым делом постарайтесь избавиться от прямых обращений к DOM–дереву, поскольку это трудоемкая операция. Давайте представим, что у вас в коде постоянно происходит какая-то манипуляция с каким-либо объектом. Вместо того, чтобы работать с этим объектом по ссылке, вы постоянно дергаете DOM-дерево для поиска данного элемента и работы с ним, а так мы реализуем паттерн кэширования в коде.


Вторым шагом избавьтесь от глобальных переменных. ES6 подарил нам замечательное изобретение человечества под названием блочные переменные (если перевести на простой язык — объявления переменных с var на let и const).


Ну и напоследок самое вкусное. Тут, к сожалению, не у всех хватает опыта понять нюанс. Я против использования рекурсивных функций. Да, они сокращают объем написанного кода, но, не обходится без подвоха: часто такие рекурсивные функции не имеют условий выхода, про них просто-напросто забывают. Как в присказке “молотком можно палец разбить, но это проблема не молотка, а владельца пальца” или анекдоте про кошек: рекурсивные функции — это не плохо, их надо уметь готовить.


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


  1. Определение длины массива
  2. Нумерация каждого из элементов
  3. Сдвиг каждого из элементов массива
  4. Вставка нового элемента в массив
  5. Повторная индексация элементов массива.

Итоги:


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


  1. Определяем на сколько все хорошо/плохо, снимаем метрики.
  2. Выпиливаем все лишнее: неиспользуемые зависимости, мертвый код, ненужные комментарии.
  3. Настраиваем и ускоряем время сборки, настраиваем разные профили для контуров.
  4. Анализируем код и решаем какие части будем оптимизировать и переписывать.
  5. Пишем тесты, чтобы не допустить потерю функциональности.
  6. Запускаем рефакторинг, избавляемся от глобальных переменных, утечек памяти, дубляжа кода и прочего мусора, и не забываем про кэширование.
  7. Упрощаем сложность вычислений и выносим всё возможное в веб-воркер.

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


И напоследок.


Я верю в вас, и в то, что у вас всё получится. Считаете меня наивным? Полагаю, вы удивитесь, но раз вы нашли эту статью, прочитали ее до конца, значит (у меня для вас есть хорошие новости) у вас есть мозги, и вы стараетесь развивать их. Успехов в столь нелегком начинании, как оптимизация фронта!

Tags:
Hubs:
+5
Comments13

Articles

Change theme settings