open atlas
↑ К треку
Паттерны React RXP · 15 · 03

Капстоун III: измерь до и после

Завершаем капстоун измерением рефакторинга: докажи меньше перерисовок (Profiler), мельче проброс, исчезнувший баг устаревания и целевое изменение как одну локальную правку — а потом остановись, ведь достаточно хорошо бьёт идеально.

RXP Senior ◷ 20 min
Уровень
ОсновыJuniorMiddleSenior

Ты провёл инвентаризацию Dashboard в части 1 и выполнил исправления в части 2: божественный компонент разбился на Toolbar, DataTable и Detail; состояние filtered, выведенное эффектом, стало значением, производным во время рендера; жирный контекст разделился; индексные ключи стали key={row.id}. Дифф выглядит отлично. Но «выглядит отлично» — ровно то заявление, которое senior-рефакторингу делать не позволено.

Рефакторинг, который нельзя измерить, нельзя и защитить. Весь смысл этого капстоуна был в том, чтобы закрыть названные пункты из инвентаризации, — так что теперь ты это докажешь. Меньше перерисовок, по Profiler. Мельче проброс пропсов. Баг устаревания доказуемо ушёл. Целевое изменение — «добавить колонку», «добавить фильтр» — теперь одна локальная правка. А потом senior-ход, который джуниоры пропускают: ты останавливаешься, потому что за точкой окупаемости любой дальнейший рефакторинг — это лишь риск без отдачи.

Цель

После этого урока ты можешь доказать, что рефакторинг React улучшил две вещи, которые важны, — поведение рендера (измеренное React Profiler: меньше коммитов, уже коммит, ниже число рендеров) и изменяемость (глубина проброса, локальность целевой правки, исчезнувший баг производного состояния), — привязать каждый результат к UI = f(state) и линзе стоимости изменения и распознать точку остановки, где достаточно хорошо бьёт идеально, так что ты не объявляешь победу без измерения и не рефакторишь за точкой окупаемости.

1

Измеряй поведение рендера через React Profiler, а не на глаз — считай коммиты и что перерисовалось в каждом. До/после запиши одно и то же взаимодействие (введи один символ в фильтр) под Profiler. Важны числа: сколько компонентов закоммитилось и какие. До нажатие клавиши перерисовывало всё дерево, потому что состояние жило слишком высоко и один жирный контекст кормил всех. После, выводя filtered во время рендера и разделив контекст, нажатие коммитит только поле Toolbar и DataTable — потребители Detail и theme нет.

// до — Profiler на одно нажатие: коммитится ~14 компонентов (вся фича)
// после — Profiler на одно нажатие: коммитится 2 (поле Toolbar + DataTable)
//
// причина, в терминах UI = f(state):
// 'filtered' теперь f(data, query), вычисляемая в рендере, и query живёт
// рядом с полем ввода — так что смена query перезапускает f только для тех частей, что её читают
const filtered = data.filter((r) => r.name.includes(query)); // ни эффекта, ни второго состояния

Это заголовочная метрика. «Ощущается быстрее» — не результат; «14 коммитов → 2 коммита на нажатие, зафиксировано в Profiler» — результат.

2

Измеряй изменяемость как глубину проброса и локальность изменения — рефакторинг должен делать следующее изменение дешёвым. Два считаемых сигнала. Первый — глубина проброса пропсов: коллбэк onSelect, который пронизывал Dashboard → Table → TableBody → Row → RowActions (четыре прыжка), теперь достигает своего потребителя напрямую через контекст или поднятую разметку — глубина 4 → 0. Второй и главный приз — локальность изменения: целевое изменение, под которое фича строилась, теперь трогает одно место.

// «добавить колонку 'status'» — ДО: правишь тип, строку <th>, map <td>,
// switch сортировки И форму fetch, разбросанные по божественному компоненту.
//
// ПОСЛЕ: колонки — это данные, рендеринг — f(columns), так что это одна локальная правка:
const columns: Column<Row>[] = [
  { key: "name", header: "Name" },
  { key: "email", header: "Email" },
  { key: "status", header: "Status" }, // <- всё изменение целиком
];
// <DataTable columns={columns} rows={filtered} /> рендерит f(columns, rows)

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

3

Докажи выигрыш в корректности так же, как доказал выигрыш в перформансе: покажи, что баг производного состояния ушёл структурно, а не просто перестал наблюдаться. P0-запах из части 1 был багом устаревания — filtered копировалось из data + query эффектом, устаревало на кадр и было хрупким к пропущенной зависимости. «Я его больше не видел» — не доказательство; эффект всё ещё мог сработать не вовремя. Доказательство структурно: нет второго состояния и нет эффекта, поэтому нет окна, в котором filtered может разойтись с data и query.

// до — два источника истины, поздно сводимые эффектом (устаревание на кадр)
const [filtered, setFiltered] = useState<Row[]>([]);
useEffect(() => { setFiltered(data.filter((r) => r.name.includes(query))); }, [data, query]);

// после — один источник истины; класс бага не может существовать, потому что нет копии
const filtered = data.filter((r) => r.name.includes(query));

Это UI = f(state) в действии: выводи, не дублируй. Выигрыш докладывается как «класс бага устранён» — устаревание не может повториться, потому что состояния, в котором оно жило, больше нет.

4

Знай, когда ОСТАНОВИТЬСЯ: у рефакторинга есть точка окупаемости, и заходить за неё — это риск без отдачи. Ты закрыл каждый пункт инвентаризации; Profiler зелёный; целевое изменение локально; баг ушёл. Джуниорский соблазн сейчас — продолжать: мемоизировать каждый компонент, вынести ещё три хука, сделать DataTable бесконечно обобщённым под колонки, которых ещё нет. Это не seniority, это то самое переусложнение, от которого весь трек предостерегает, теперь в одежде рефакторинга. Каждая лишняя абстракция добавляет косвенность и шанс внести регресс — ради спекулятивного будущего изменения, которое может никогда не наступить.

// сигнал СТОП: следующее «улучшение» оптимизирует изменение, которого никто не просил.
// - memo() на компоненте, который Profiler коммитит дважды за сессию → нет окупаемости
// - универсальная плагин-система <DataTable> ради одной таблицы → YAGNI, чистая стоимость
// достаточно хорошо (измерено, локально, корректно) бьёт идеально (спекулятивно, рискованно)

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

Разбор примера

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

// РЕЗУЛЬТАТ РЕФАКТОРИНГА — фича Dashboard  (каждая строка закрывает P0/P1/P2 из части 1)
//
// метрика                       до              после        как измерено
// ----------------------------------------------------------------------------
// коммиты на одно нажатие        ~14 (всё)       2            React Profiler, то же взаимодействие
// перерисованные компоненты      Toolbar+Table   Toolbar+      Profiler «что отрендерилось»
//   на смену фильтра             +Detail+theme   DataTable
// глубина проброса (onSelect)    4 прыжка        0 прыжков    счёт компонентов-проводников
// изменение «добавить колонку»   5 разбросанных  1 локальная  счёт правок для добавления 'status'
//   локальность                  правок          правка
// устаревание производного (P0)  есть            устранено    нет второго состояния / нет эффекта
// порча по индекс-ключам (P0)    есть            исправлено   key={row.id}; переупорядочение хранит состояние строки

Теперь прочти это через две линзы, на которых построен трек. UI = f(state): выигрыш в рендере существует, потому что filtered вычисляется из состояния во время рендера, а само состояние переехало к своему читателю, так что f перезапускается только там, где менялись её входы, — это и есть падение числа коммитов. Стоимость изменения: строка «добавить колонку», ушедшая с 5 правок до 1, — это всё оправдание рефакторинга; более быстрый рендер, не удешевивший изменение, был бы слабым результатом, а более дешёвое изменение без выигрыша в перформансе — всё равно настоящий выигрыш.

Затем проверка остановки. Каждый пункт P0/P1/P2 закрыт. Следующий кандидат — обернуть DataTable в memo — Profiler показывает, что он коммитится дважды за сессию, так что измеримо ничего не сэкономит. Ты останавливаешься здесь. Написать эту таблицу, привязать её к двум линзам и выбрать остановиться — и есть senior-артефакт. Дифф был лёгкой частью.

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

Зачем вообще измерять, когда код-после очевидно лучше? Потому что «очевидно лучше» — это то, как рефакторинги тихо проваливают ревью и тихо не окупаются. Ревьюер не может одобрить заявление о перформансе, которое не видит; будущий-ты не может доверять, что баг устаревания ушёл, если никто не записал, почему он не может повториться. Измерение превращает рефакторинг из эстетического аргумента («чище») в инженерный («14 → 2 коммита, добавить колонку ушло с 5 правок до 1, класс бага устаревания устранён»). Первый вид обрастает спорами о цвете велосипеда; второй вид мёржится. Измерение — это то, что делает работу защитимой для того, кто не видел, как ты её делал.

Частая ошибка

Два симметричных режима отказа обрамляют этот урок. Первый — объявить успех, не измерив: сдать «отрефакторил Dashboard, гораздо чище» без захвата Profiler и без счёта локальности изменения — ты мог переместить строки, не сократив рендеры, или даже добавить коммиты. Второй — рефакторить за точкой окупаемости: Profiler уже зелёный и изменение уже локально, но ты продолжаешь мемоизировать, выносить и обобщать ради изменений, которых никто не просил, добавляя косвенность и риск регресса ради нулевого измеренного выигрыша. Senior-суждение живёт ровно между ними: докажи выигрыш числами и остановись в тот момент, когда окупаемость следующей правки больше не превышает стоимость абстракции. Достаточно хорошо, измеренное, бьёт идеально, воображаемое.

Проверь себя
Викторина

Ты завершил рефакторинг Dashboard. Profiler показывает, что нажатие клавиши ушло с ~14 коммитов до 2, а «добавить колонку» ушло с 5 разбросанных правок до 1. Коллега предлагает обернуть каждый листовой компонент в memo() «для тщательности», прежде чем считать дело готовым. Каков senior-выбор?

Итог

Рефакторинг завершается доказательством, а не вайбами. Измерь две вещи, которые важны: поведение рендера через React Profiler (число коммитов и что перерисовалось — ~14 → 2 на нажатие это результат; «ощущается быстрее» нет) и изменяемость (глубина проброса 4 → 0 и целевое изменение, ушедшее с разбросанных правок до одной локальной правки), плюс выигрыш в корректности, показанный структурно, — баг устаревания производного состояния ушёл как класс, потому что дублированного состояния и его эффекта больше нет. Привяжи каждое число к двум линзам трека: падение рендеров — это UI = f(state), перезапускающаяся только там, где сдвинулись её входы, а падение локальности изменения — это линза стоимости изменения: рефакторинг, не удешевивший следующее изменение, едва ли рефакторил. Сдай таблицу до/после, где каждая строка закрывает названный пункт инвентаризации. Затем остановись: когда каждый пункт закрыт, а следующее «улучшение» оптимизирует изменение, которого никто не просил, любой рефакторинг — это риск без отдачи. Senior-планка — доказать улучшение в рендере и в изменяемости и знать, что достаточно хорошо, измеренное, бьёт идеально, воображаемое. Это суждение — соразмерить работу окупаемости и бросить на черте — и есть то, на что этот трек указывал всё это время.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 4 завершено

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.