awesome-everything EN
↑ Обратно к восхождению

Архитектура фронтенда

Ревью архитектуры фронтенда: порядок и каскадные сбои

Суть Сеньор ревьюит архитектуру снизу вверх — сначала форма состояния, последним build-пайплайн — потому что нижние слои каскадируют. Плохая форма состояния даёт перерендер-штормы, которые не починит никакая разбивка кода; слои взаимодействуют, и порядок ревью — это и есть урок.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на junior-высоте — поверхность
◷ 17 min

Команда эскалирует: дашборд тормозит на каждом нажатии клавиши, и они заводят тикет «добавить разбивку кода и лениво грузить графики». Ты профилируешь. Дело не в графиках. Нажатия в форме пишутся в глобальный стор, каждый подписчик перерендеривается на каждый символ, и поддерево графика — один из них. Разбивка бандла графика ничего не меняет — перерендер срабатывает уже после загрузки чанка. Фикс был тремя юнитами раньше: нажатия — это локальное UI-состояние, им не место в глобальном сторе. Команда оптимизировала последний слой, чтобы замазать баг первого.

Весь трек — это одно дерево решений

Каждый юнит до этого учил решению по отдельности: форма состояния, загрузка данных, доступные формы, дизайн-токены, границы монорепо, разбивка кода, build-пайплайны. В реальном приложении это не семь отдельных выборов — это один стек, и у стека есть направление. Нижние слои ограничивают верхние. Форма состояния решает радиус перерендеров; токены решают, будет ли ребрендинг сменой конфига или миграцией; границы монорепо решают время CI; разбивка кода и build-пайплайн решают, что реально уезжает в браузер.

Ход сеньора при ревью нового фронтенда — читать не сверху вниз (структура файлов, потом компоненты, потом состояние), а снизу вверх по цене сбоя. Самые дешёвые в починке баги — в пайплайне; самые дорогие — в форме состояния, потому что всё ниже по течению их наследует. Поэтому ты оцениваешь слои в том порядке, где ошибка наносит больше всего урона, и перестаёшь оптимизировать слой в тот момент, когда понимаешь, что настоящий баг живёт ниже.

Каскад: где всплывает сбой каждого слоя

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

Прочитай каскад и в другую сторону. Отсутствие системы токенов всплывает как боль ребрендинга: маркетинговый ребрендинг, который должен быть однострочной сменой примитивных значений, превращается в hunt-and-replace по сотням файлов. Atlassian и любая зрелая дизайн-система структурируют токены в три слоя — примитивы (сырая палитра), семантика (что цвет значит: text-danger, surface-raised) и компонентные значения. Ребрендинг = сменить примитивы. Тёмная тема = сменить семантику, компоненты не трогаются. Команды, захардкодившие #1a73e8 повсюду, вместо этого платят неделями; одна задокументированная миграция 200 компонентов на тёмную тему заняла два дня с семантическим слоем токенов и заняла бы недели find-and-replace без него.

Слой (порядок ревью)Симптом при поломкеПочему верхний слой не починит
1. Форма состоянияПеререндер-штормы, расходящиеся производные значения, устаревшие после мутации viewРазбивка/мемо работают после лишнего рендера; радиус взрыва задаёт то, где живёт состояние
2. Загрузка данныхВодопады запросов, медленный LCP, двойные фетчиБыстрый бандл всё равно сериализует N round-trip’ов, если граф фетчей последовательный
3. ТокеныБоль ребрендинга, несогласованный UI, переписывание тёмной темыЗахардкоженные значения — не проблема сборки; отсутствует источник истины
4. Границы монорепоВзрыв времени сборки, связанность пакетов, медленный CIБандлеры подчиняются твоему графу зависимостей; плохие границы пересобирают всё на любое изменение
5. Разбивка кодаОгромный начальный бандл, медленный Time-to-InteractiveЧинится только здесь — но только если слои 1–4 в порядке
6. Build-пайплайнУезжает мёртвый код, нет tree-shaking, медленный фидбек CIДешевле всего чинится; смена конфига, а не архитектуры

Границы монорепо — множитель времени сборки

Слой, который чаще всего винят в медленном CI — «сборка медленная, купим раннеры побыстрее» — обычно это проблема границ, а не железа. Если у пакетов нет настоящих границ (всё импортит всё, один гигантский пакет shared, от которого зависит каждое приложение), то любое изменение инвалидирует кэш всего графа, и CI пересобирает всё. Фикс структурный: чистые границы пакетов плюс выполнение только затронутого (affected-only), чтобы изменение одного пакета собирало один пакет.

Цифры жёсткие. Mercari сообщил о сокращении длительности задач Turborepo примерно на 50% и общего времени CI-джоба на ~30% после добавления удалённого кэша и тюнинга воркфлоу. Задокументированные сетапы монорепо на GitHub Actions достигают 12-кратного сокращения CI, комбинируя affected-only-выполнение, удалённый кэш и динамическую матрицу; 15-минутная сборка падает до 2–3 минут, когда изменился только один пакет, а остальное — попадания в кэш, и удалённый кэш может убрать 80–90% общего времени сборки для большой команды, потому что каждый CI-ран делится работой вместо пересчёта. Но самый большой рычаг — выше любого кэша: выполнение только затронутого. Запустить 4 пакета вместо 45 бьёт любую оптимизацию кэширования, и до этого ты доходишь, только если границы настоящие. Быстрый бандлер не спасёт граф зависимостей, где каждый лист зависит от ствола.

Почему это работает

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

Build-пайплайн — самый дешёвый слой; чини его последним, а не первым

Пайплайн (конфиг бандлера, tree-shaking, минификация, интеграция с CI) — это место, с которого команды обожают начинать, потому что оно ощущается измеримым: включил флаг, смотришь, как падает цифра. Панель Coverage в Chrome DevTools регулярно показывает, что 20–30% уехавшего JavaScript никогда не выполняется на данной странице — реальные, возвращаемые трафик и время парсинга. Это стоит делать. Но это последнее, что ревьюит сеньор, потому что это смена конфига, а не архитектуры, а конфиг дёшево чинить потом. Оптимизировать пайплайн, пока сломана форма состояния, — инженерный эквивалент полировки машины с заклинившим двигателем: поверхность выглядит быстрее, машина — нет.

Дисциплина в том, чтобы порядок цены сбоя вёл ревью: подтверди форму состояния и граф фетчей (вместе они владеют интерактивностью и LCP — целься в LCP меньше 2.5 с), затем токены (цена ребрендинга/темизации), затем границы (время CI), и только потом разбивай бандлы и тюнь пайплайн. Каждый верхний слой предполагает, что нижние в порядке; переверни порядок — и будешь оптимизировать симптомы.

Выбери лучший вариант

Новый внутренний инструмент: одно приложение, небольшая дизайн-команда с ребрендингом примерно раз в год, команда из 5 инженеров, ~12 запланированных пакетов, графики тяжёлые, но открываются редко. Выбери подходящую архитектуру.

Викторина

Дашборд перерендеривается на каждое нажатие в поле поиска. Коллега предлагает разбить графики на чанки, чтобы починить. Как читает сеньор?

Викторина

CI занимает 15 минут на однострочное изменение в одном из 12 пакетов. Что бьёт по корневой причине?

Расставь шаги по порядку

Расставь слои, которые сеньор оценивает при ревью нового фронтенда (сначала самое дорогое в починке):

  1. 1 Форма состояния — выводимое vs хранимое, серверный кэш vs клиентское состояние, колокация (задаёт радиус перерендеров)
  2. 2 Загрузка данных — водопады vs параллелизм, что владеет LCP
  3. 3 Дизайн-токены — слои примитивы→семантика, чтобы ребрендинг/тёмная тема были сменой, а не миграцией
  4. 4 Границы монорепо — чистые пакеты + affected-only CI, множитель времени сборки
  5. 5 Разбивка кода — route/компонентные чанки, lazy-load тяжёлого и редкого
  6. 6 Build-пайплайн — tree-shaking, удаление мёртвого кода, интеграция с CI (дешевле всего, чини последним)
Вспомните перед уходом
  1. 01
    Команда винит тормозящий дашборд в отсутствии разбивки кода. Пройди, как ты нашёл бы настоящий слой и почему их фикс не сработает.
  2. 02
    Почему сеньор ревьюит архитектуру снизу вверх по цене сбоя, а не сверху вниз по структуре файлов, и какие слои самые дорогие, а какие — дешёвые?
Итог

Весь трек схлопывается в одно правило: слои фронтенда каскадируют, поэтому сеньор ревьюит их снизу вверх по цене сбоя, а не сверху вниз по структуре файлов. Форма состояния — самая дорогая, потому что задаёт радиус перерендеров — высокочастотное состояние в глобальном сторе даёт перерендер-штормы, которые не чинит ни разбивка кода, ни мемоизация, потому что они работают после лишнего рендера. Над ней — граф фетчей (водопады владеют LCP), затем токены (слой примитивы→семантика делает ребрендинг сменой значений, а не многонедельной охотой по файлам — два дня против недель для миграции тёмной темы), затем границы монорепо (множитель времени сборки; чистые границы плюс affected-only-выполнение и удалённый кэш превратили 15-минутную сборку в 2–3 минуты и сняли ~50% времени задач Mercari). Разбивка кода и build-пайплайн идут последними, потому что это самые дешёвые слои — конфиг, а не архитектура. Повторяющаяся ловушка — принимать симптом в верхнем слое за баг этого слоя; долговечный фикс всегда у самого нижнего сломанного слоя, и ревью в порядке цены сбоя — это как ты его находишь.

Продолжить восхождение ↑Собираем вместе: тест с выбором ответа
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources4
expand
  1. 01
  2. 02
  3. 03
  4. 04

Trademarks belong to their respective owners. Editorial reference only.