Головна / Статті / Як незначні рішення накопичуються у довгострокових кодбазах React

Як незначні рішення накопичуються у довгострокових кодбазах React

П’ятнадцять звичок технічного обслуговування для React-додатків, які прослужать роками: читабельний код, зосереджені компоненти, обмежений стан, мінімальна кількість залежностей, тести та моніторинг.

2206 слів

Створення проекту на React — це проста частина. Справжній випробування настають через кілька місяців чи років, коли у додатку з’являється більше користувачів, функцій, учасників розробки та залежностей, а кілька „тимчасових“ рішень тихо стають основою функціонування. У такий момент сам React рідко є проблемою; справа в тому, щоб зберегти код базу зрозумілою, поки все навколо постійно змінюється. Цей посібник розглядає п’ятнадцять звичок, розділених за місцями, де насправді накопичуються витрати на технічне обслуговування, щоб ви могли виявити рішення, які поступово погіршують ситуацію, перш ніж код база стане такою, що ніхто не хоче торкатися.

Код, написаний для наступного читача

Віддавайте перевагу зрозумілому коду перед хитромудрим

Щільні однорядкові конструкції, глибокі абстракції та функції-допоміжники, створені для десятків гіпотетичних сценаріїв, здаються продуктивними під час їх написання. Через шість місяців вони перетворюються на загадку, часто для самого автора, який більше не пам’ятає причини.

Альтернативою є навмисно простий код. Розгляньте можливість відображення екрана, який може бути у процесі завантаження, з помилкою чи готовим. Ранні повернення роблять кожен стан очевидним, по одній умові за раз:

if (isLoading) {
  return <LoadingState />;
}

Гілка з помилкою має таку саму структуру, а оптимальний шлях йде в кінці:

if (error) {
  return <ErrorState />;
}
return <Dashboard />;

Тут немає нічого вражаючого, і саме це й мета: будь-хто може за кілька секунд побачити, який компонент відображається та коли. У великому додатку те, наскільки швидко наступний розробник зрозуміє ваш код, має набагато більше значення, ніж враження на поточного.

Назви — це документація, яка ніколи не старіє

Під час написання коду назви здаються дрібницею, але через пів року під час виправлення помилок вони стають дуже важливими. Порівняйте пошук у кодовій базі за цим:

handleData()

з пошуком за цим:

calculateMonthlyRevenue()

Друга назва дозволяє зрозуміти, що обчислює функція, ще до того, як ви відкриєте файл. Великі кодові бази переважно є каналом комунікації між розробниками; точна назва виражає мету, а нечітка змушує кожного читача ставати детективом.

Уважно ставіться до універсальних назв, які з’являються під тиском дедлайну:

data
item
temp
helper
value
thing

Так, thing справді зустрічається у реальних кодових базах. Корисне правило: якщо назва підходить однаково добре до будь-якого файлу проекту, вона, ймовірно, недостатньо інформативна для цього конкретного файлу.

Межі компонентів та стану

Надто великі компоненти стають дорогими у керуванні

Майже кожен довгостроковий проект на React з часом накопичує компоненти, які мають по чотири цифри у кількості рядків. Рідко коли все починається саме так — спочатку їх буває приблизно 150 рядків, потім з’являється модальне вікно, потім фільтри, потім завантаження даних, потім перевірки дозволів, а ще одне модальне вікно. Зрештою, коли ви відкриєте файл, там можна знайти щось на кшталт цього:

Dashboard.tsx
1,247 lines

Компоненти такого розміру важко зрозуміти, протестувати, повторно використовувати та відлагоджувати, а їх зміна є ризикованою, оскільки будь-яка зміна може вплинути на стан, від якого залежать інші частини файлу. Розділяйте код раніше, ніж це здається необхідним. Замість одного файлу:

Dashboard.tsx

розділіть екран на частини, кожна з яких виконуватиме окрему функцію:

DashboardHeader.tsx
DashboardStats.tsx
DashboardFilters.tsx
RecentActivity.tsx
DashboardTable.tsx

Тепер кожен файл має одну функцію та значно менший радіус впливу. Практичний критерій: якщо ви не можете описати компонент одним реченням без кількох слів „та“, його потрібно розділити.

Використовуйте ті шаблони, які ви вже бачили, а не ті, які уявляєте

Компоненти, які можна повторно використовувати, — це хороший підхід, але його легко перестаратися. Поширеним прикладом невдачі є ситуація, коли одна кнопка намагається виконувати функції всіх кнопок у продукті:

<UniversalButton
  type="primary"
  variant="rounded"
  size="medium"
  iconPosition="left"
  loadingStyle="spinner"
/>

Кожен новий параметр здається безневинним, але разом вони створюють простір комбінацій варіантів, які ніхто повністю не тестує, і такий „повторно використовуваний“ компонент у кінцевому підсумку виявляється складнішим у використанні, ніж три невеликі, спеціалізовані кнопки. Повторне використання має цінність; преждчасна узагальнення — ні.

Витягуйте спільну абстракцію лише тоді, коли бачите, що шаблон дійсно повторюється, а не тому, що майбутня сторінка може її потребувати. Якщо така потреба виникне, ви створите абстракцію на основі реальних прикладів, а не припущень.

Впорядковуйте стан за місцем його розташування

Керування станом часто погіршується непомітно. Невеликий додаток починається зі стану компонентів:

useState()

Потім додається спільний стан через контекст:

useContext()

І зрештою проект містить одночасно Redux, кілька контекстів, локальний стан, стан URL та серверний стан, без чіткої відповіді на те, який з них керує бічною панеллю. Кожен інструмент здавався доцільним під час додавання; бракує правила щодо того, що має знаходитися де.

Простий спосіб відновити порядок — це класифікувати стан за його природою. Локальний UI-стан охоплює такі речі:

modal open
dropdown selected
input value

Він має залишатися всередині компонента, який його використовує. Серверний стан — це дані, які знаходяться на серверній частині та лише кешуються в браузері:

users
products
analytics

Для такої категорії використовуйте бібліотеку, призначену для отримання, кешування та перевірки актуальності віддалених даних, замість того щоб вручну копіювати відповіді до глобального сховища. Якщо ваша команда розглядає таку зміну, переваги та недоліки описані у нашому порівнянні React Query та Redux для керування станом сервера. Нарешті, справді глобальний стан складається з невеликого списку елементів:

theme
authenticated user
app-wide preferences

Зробіть цю останню групу елементів мінімальною: будь-який компонент може читати або змінювати глобальний стан, тож чим менше його елементів, тим менше помилок, які здаються безпричинними. Фільтри, вкладки та сторінкування часто мають знаходитися у URL, де вони залишаються після перезавантаження та можуть бути поширені.

Структура та залежності

Зробіть структуру папок передбачуваною

src/
components/
shared/
common/
helpers/
utils/
services/
core/
misc/
new/
new2/

Куди слід розмістити новий компонент профілю користувача? Ніхто не може сказати, тому кожен розробник обирає інакше, і структура постійно змінюється. Перекриваючі один одного категорії на кшталт shared, common, helpers та utils свідчать про те, що ніхто не визначив значення кожної з них.

Макет, заснований на функціях, усуває більшу частину цієї неоднозначності, групуючи код відповідно до тієї частини продукту, якій він слугує:

features/
  auth/
  dashboard/
  users/
  billing/

У межах кожної функції невелика, повторювана група підкаталогів допомагає зберігати порядок:

components/
hooks/
services/
types/

Тепер кожен, хто працює над білінгом, знає, де знаходиться код білінгу, і для переписування функціоналу потрібно редагувати лише один каталог замість десяти. Можуть працювати й інші схеми організації (див. наше порівняння структур папок у React); головне — щоб правило було передбачуваним та зафіксованим.

Розглядайте кожну залежність як потенційну проблему під час технічного обслуговування

Додавання пакета вимагає однієї команди:

npm install something-cool

Витрати проявляються пізніше: пакети залишаються невикористаними, вони вносять критичні зміни, створюють проблеми з безпекою та збільшують розмір бандлу, а ваша команда змушена постійно оновлювати код, який ніхто з неї не читав.

Тож перед встановленням запитайте себе, чи справді вам це потрібно. Пакет, який допомагає заощадити значні зусилля або вирішує справді складну проблему, наприклад роботу з датами з урахуванням часових поясів, заслуговує на своє місце. Той, що лише форматує рядок, — ні.

Механізми безпеки, які розвиваються разом із додатком

Перевіряйте ті процеси, які завдадуть найбільшої шкоди у разі збою

У невеликому додатку достатньо просто пройтися по екранах перед випуском. У великому додатку редагування однієї функції може спричинити проблеми з оплатою з причин, які ніхто не може пояснити, і саме тоді автоматизовані тести стають корисними: вони дозволяють вам з упевненістю змінювати код, який ви не писали.

Вам не потрібно охоплювати кожну деталь реалізації. Зосередьтесь на процесах для користувачів, де збій має великі наслідки:

  • увійшовши в систему
  • процес оформлення покупки
  • надсилання важливих форм
  • перевірка дозволів
  • обчислення, від яких залежить бізнес

Тести не доведуть, що додаток бездоганний; вони виявляють очевидні збої раніше, ніж це зроблять користувачі. Тести поведінки, а не внутрішньої структури, також краще переживають процес рефакторингу.

Стежте за повільним, кумулятивним погіршенням продуктивності

Великі додатки на React рідко стають повільними після одного випуску. Продуктивність погіршується через кожне розумне рішення: велику залежність, величезне зображення, зайве оновлення інтерфейсу, десять запитів на одному екрані. Разом вони можуть зробити завантаження панелі керування тривалим на п’ять секунд, тому постійно стежте за:

  • оновленням компонентів, коли нічого з того, що вони відображають, не змінилося
  • зростанням розміру пакету з кожним випуском
  • більш ніж одним викликом того самого запиту на екрані
  • окремими компонентами, які повільно відображаються
  • довгими списками, відображеними без віртуалізації
  • надто великими зображеннями

Невеликі успіхи накопичуються, а виявлення регресії в момент її появи значно дешевше, ніж її пошук пізніше.

Навмисно створюйте сценарії невдач

Більшість зусиль спрямовані на „щасливий“ сценарій, коли кожен запит успішний. У реальному середовищі з’єднання втрачаються, API не працюють, права змінюються, а бекенд повертає дані, яких ніхто не очікував. Користувач має бачити зрозуміле повідомлення про проблему на кшталт „Ми не змогли завантажити ваші дані. Спробуйте ще раз“, а не первинну помилку виконання, наприклад:

TypeError: Cannot read properties of undefined

У термінах React це означає чіткі стани помилок під час отримання даних, межі обробки помилок, щоб одна несправна компонента не зробила сторінку порожньою, та можливості повторної спроби там, де це доцільно.

Вважайте моніторинг у реальному середовищі частиною розробки

Випуск продукту — це не кінець роботи. У реальному середовищі виникають проблеми, яких немає під час локальної розробки, і вам потрібен огляд:

  • проблеми під час виконання та місця їх виникнення
  • запити, які виконуються повільніше, ніж очікувалося
  • запити до API, які зазнають невдач
  • загальна продуктивність сторінки та взаємодії
  • спосіб, яким користувачі фактично використовують продукт

Без цього звіт про помилку — це просто „користувач сказав, що щось зламалося вчора“. Відстеження помилок та контекстуальні журнали перетворюють це на стек-трейс та часову позначку, за якими можна діяти.

Звички, які підтримують узгодженість команди

Створюйте документацію для людей, які вже є у команді

Документацію часто сприймають як обов’язок під час передачі обов’язків, проте вона допомагає всім у команді сьогодні, особливо щодо складних прав доступу, незвичайної архітектури, інтеграцій з третіми сторонами та важливих бізнес-правил.

Не потрібен довгий посібник. Короткі примітки про те, як працює автентифікація, як передаються дані, де знаходяться основні функції та чому були прийняті певні рішення, допомагають заощадити години. Звернення до первісного розробника перестає бути ефективним після його відходу, а у довгострокових проектах це завжди трапляється.

Рефакторинг — це рутинне технічне обслуговування, а не визнання невдачі

Рефакторинг не є доказом того, що початковий код був поганим. Вимоги, команди та продукти змінюються, і код, який підходив рік тому, може більше не вирішувати поставленої проблеми.

Небезпека полягає у твердженні, що щось „вже працює“. Так само працює стілець на трьох ногах, поки хтось на нього не сяде. Невеликі, регулярні рефакторинги з використанням тестів допомагають тримати технічний борг під контролем.

Єдність важливіша за особисті уподобання

Один розробник пише ідентифікатори так:

camelCase

Інший віддає перевагу такому формату:

snake_case

А ще кожні кілька тижнів хтось вигадує новий шаблон компонента. Велика команда не може ефективно працювати, коли кожен файл відповідає смакам його автора, тому необхідно зробити послідовність автоматичною за допомогою:

  • інструменту для перевірки коду з узгодженими правилами
  • автоматичного форматувальника
  • документованих правил найменування
  • спільних, перевірених шаблонів для поширених завдань

База коду має виглядати як єдиний проект, а не сукупність файлів, створених десятком розробників із різними підходами.

Оберіть архітектуру, яку ваша команда може справді впровадити

Дискусії про архітектуру — це улюблена розвага: мікросервіси, монорепозиторії, Clean Architecture, доменно-орієнтоване проектування, і завжди хтось має готову діаграму. Але найскладніший варіант не завжди є найкращим. Хороший вибір проймає три критерії:

  • він вирішує реальні проблеми, з якими ви стикаєтесь
  • вся команда може його пояснити
  • команда може підтримувати його у робочому стані та розвивати
  • Простий дизайн, застосовуваний послідовно, завжди кращий за блискучий дизайн, який розуміє лише його творець.

    Чому все це не є захоплюючим та чому це ефективно

    Довгострокове обслуговування змінює значення поняття „хорошого розроблення“: швидкість написання коду має менше значення, ніж читабельність, легкість майбутніх змін, потреби інших розробників та поведінка продукту у реальних умовах. Ось чек-лист для роботи:

    • Компоненти залишаються невеликими та виконують одну функцію
    • Глобальний стан — це короткий, продуманий список
    • Назви описують призначення
    • Кожен новий пакет має обґрунтування
    • Структура папок дотримується одного задокументованого правила
    • Рефакторинг відбувається постійно
    • Критичні користувацькі процеси мають тести
    • Помилки у продакшені є видимими
    • У разі рівності варіантів перемагає простіший

    Ці правила не є привабливими, і саме тому вони залишаються актуальними. Щодо практик структурування на рівні архітектури, дивіться десять архітектурних звичок, які допомагають підтримувати кодові бази фронтенду протягом багатьох років.

    Ключові висновки

    • Великі додатки на React рідко мають проблеми через сам React; проблеми виникають через накопичення дрібних ускорень: один величезний компонент, одна загадкова утиліта, один зайвий пакет, один хак, який мав бути тимчасовим.
    • Здатність до підтримки не можна додати в кінці процесу. Це сума багатьох дрібних рішень, прийнятих поступово.
    • Найкращий момент для застосування цих звичок — ще до появи проблем, коли розділення компонента чи відмова від залежності коштують лише кілька хвилин, а не тижнів.
  • Коли вас спокушає хитрий рішення, пам’ятайте, що людина, яка буде його підтримувати через шість місяців, можливо, це будете ви самі, муситиме зрозуміти, чому воно працює без поточного контексту.
  • Пов’язана література