Frontend-оптимизация: от архитектуры до MVP — Блог Work Solutions
Ускоряем frontend-разработку: эффективные методы и инструменты
ГлавнаяБлогБизнесуфронтенд, разработка, модульная архитектура, MVP
Бизнесу28 августа 2024

Ускоряем frontend-разработку: эффективные методы и инструменты

Фотография автора
Геворг ДанелянFrontend-техлид

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

Варианты оптимизации

Для начала подумаем над тем, что именно можно оптимизировать? Чтобы ответить на этот вопрос, нужно сначала ответить на другой вопрос: Что общего между frontend-приложениями?

Предлагаем сначала разделить весь код приложения на технический и бизнесовый. После разработки нескольких приложений, с каждым новым начинаешь ловить себя на мысли, что технический код повторяется в том или ином виде. Сразу появляется резонный вопрос: «А почему бы мне не вынести этот код в отдельные модули и не переиспользовать их?» Забегая вперед скажу, что это «совершенно верный подход», но не всегда он позволяет решать задачи быстрее.

Какие задачи нам предстоит решить при старте работ над новым проектом? Ну, сначала нужно выбрать фреймворк: react, vue или angular. Затем выбрать state-manager, желательно проверенный временем: например redux или pinia.

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

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

Так, хорошо. А что же мы тогда можем оптимизировать? Чтобы ответить на этот вопрос, предлагаем составить список задач, которые приходится решать в большинстве фронтенд-приложений:

  • Отправка запросов;
  • Роутинг;
  • Обработка query-параметров;
  • State management;
  • Работа с датами;
  • Описание интерфейсов (если речь идет о typescript);
  • Cookie;
  • LocalStorage;
  • Файловая структура.

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

Единая архитектура

Единая архитектура под все frontend-приложения — это мечта бизнеса и разработки. Идеальный мир, в котором и овцы целы, и волки сыты. Разумеется в реальности добиться этого сложно, но все же, насколько эта идея призрачна?

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

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

Предположим, мы подготовили набор модулей на все случаи жизни и даже смогли решить одну из самых сложных проблем современности: научили frontend-разработчиков думать не технологиями, а подходами. Но как быть с файловой структурой и методикой разделения компонентов, как заставить разработчиков мыслить в едином стиле? Тут нам поможет подход Feature-Sliced Design. Можно долго спорить о его преимуществах и недостатках, но выделим только главные моменты. А именно:

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

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

Архитектурные границы как инструмент оптимизации

Изображение статьи

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

Граница между клиентом и сервером

Предположим, что создаем MVP-версию приложения. Как это часто бывает, разработка серверной части не успевает за клиентской. Обычно в таких ситуациях frontend-разработчики вынуждены продолжить работу на моковых данных. Но часто приходится видеть захардкоженные данные прямо в компонентах. А дальнейший процесс интеграции с api может повлечь за собой рефакторинг всех задействованных компонентов или даже страниц. Это неверный подход для решения задачи. Более того, он увеличит конечную эстимацию по задаче.

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

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

Граница между бизнес-логикой и представлением

Еще одним примером может служить граница между бизнес-логикой и представлением. 

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

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

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

Готовые модули

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

Отправка запросов

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

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

State management

Обычно при сочетании слов state management сразу приходят на ум redux, mobx, pinia и прочие state-менеджеры. Тут мы должны сразу вспомнить о принципе «чистых зависимостей». Классы-состояния нашего приложения не должны напрямую зависеть от конкретной библиотеки. Более того, зачастую механизмов ui-фреймверков достаточно, чтобы управлять состоянием без state-менеджера.

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

  1. Entity — это обычные данные, которые будут использоваться для отображения. Не нуждаются в выделенном классе. Основное требование — соответствие интерфейсу ассоциируемой сущности;
  2. Model — в отличие от entity, модель используется уже не только для отображения, но и для взаимодействия (мутации). Для данного типа данных предусмотрены самостоятельные классы. Базовая логика вынесена в абстрактный класс Model;
  3. Creation model — тот же model, но используется только для создания;
  4. Pagination, sorting, filter — тут из названия уже следует, что базовая логика выделена в один из соответствующих классов PaginationParams, SortingParams, FilterParams;
  5. Table data — табличные данные. Имеют встроенные модели пагинации, фильтрации и сортировки. Базовая логика вынесена в абстрактный класс TableModule.

В совокупности связка базовых классов Model, PaginationParams, SortingParams, FilterParams и TableModule позволяют нам быстро и качественно описать управление состоянием. Это самое важное в едином стиле вне зависимости от выбранного ui-фреймворка.

Cookie & LocalStorage

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

Думаем, все, кто использовал хоть раз localStorage, сталкивались с необходимостью конвертации объекта в json и обратной операцией парсинга. А что если, после вызова метода getItem, мы хотим убедиться в том, что данные соответствуют ожидаемому интерфейсу? Тут уже просто JSON.parse будет недостаточно. Такие надстройки на базовый api обязательно появятся. И чем раньше уйти к абстракции, тем проще и быстрее будет дальнейшее расширение. Обычно эти базовые надстройки одни и те же из проекта в проект. У нас данные абстракции реализованы в виде классов CookieStorage и LocalStorage.

Prettier & Eslint

Еще одна дыра в эстимейте задачи — ревью, так как есть много подходов для оптимизации этого процесса.

Изображение статьи
В рамках этой статьи поговорим про code-style. А так ли важна единая стилистика кода?

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

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

Шаблон frontend приложения

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

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

Open Source и накопленный опыт из других проектов

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

Open Source

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

Изображение статьи

Причина часто заключается в «грязной зависимости». Предположим, на стадии разработки MVP-версии проекта, заказчик решил сэкономить на дизайн-системе и ограничиться кастомизацией готовой ui-kit библиотекой. Но время шло, проект успешно запустился, и появилась потребность в собственной дизайн-системе. И вот разработчикам уже поставили задачу на оценку разработки и внедрения, но, взглянув на код, они ужаснулись от объема работ. А вся причина в том, что ui-kit библиотека использовалась напрямую, и теперь для ее замены нужно провести рефакторинг каждого компонента в приложении. 

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

Седина

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

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

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

Заключение

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

273
42

Другие статьи

Ко всем статьям
Фоновое изображение: четверть круга закрыват часть круга

Интересные статьи и кейсы
от Work Solutions

Нажимая кнопку «Подписаться», я даю согласие на обработку персональных данных

Спасибо за подписку!

Фоновое изображение: верхний полукруг