Головна / Статті

Читабельний JavaScript на прикладах: Десять кейсів рефакторингу «до» та «після»

Десять невеликих оптимізацій коду на JavaScript та React — від найменувань та об’єктів параметрів до обробки помилок та форматування — які полегшують читання та зміну коду для наступного розробника.

Видавець: REACTAPP.TOP7 хв читання1583 слів

Джерела: 10 Clean Code Principles I Wish I Knew When I Started Coding

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

1. Називайте значення відповідно до того, що вони представляють

Короткі, загадкові назви змушують читача шукати інформацію в іншому місці. У цьому уривку нічого не говорить про те, що означає d чи яку властивість містить a.

const d = 30;
const x = users.filter(u => u.a);

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

const trialPeriodDays = 30;

А фільтр тепер читається як речення про активних користувачів:

const activeUsers = users.filter(user => user.isActive);

Хороша назва пояснює, що означає значення; універсальні назви на кшталт data майже нічого не говорять.

const data = getData();

Назва, яка відображає концепцію сфери діяльності, є набагато інформативнішою:

const customerOrders = getCustomerOrders();

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

2. Надайте кожній функції одну завдання

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

function createUser(user) {
  // validate user
  // format data
  // save to database
  // send email
  // log activity
}

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

function createUser(user) {
  validateUser(user);

І далі йде форматування, збереження та лист-привітання — кожна з цих операцій передана у власну функцію:

  const formattedUser = formatUser(user);  saveUser(formattedUser);  sendWelcomeEmail(formattedUser);
}

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

3. Усунення дублювання, яке має спільну причину змін

Повторювана логіка є кандидатом на вилучення. Тут одна й та сама формула оподаткування зустрічається тричі.

const total1 = price1 + price1 * 0.15;
const total2 = price2 + price2 * 0.15;
const total3 = price3 + price3 * 0.15;

Перенесення її у функцію надає правилу ім’я та єдине місце розташування:

function calculatePriceWithTax(price) {
  return price + price * 0.15;
}

Кожне місце виклику потім просто застосовує це правило:

const total1 = calculatePriceWithTax(price1);
const total2 = calculatePriceWithTax(price2);
const total3 = calculatePriceWithTax(price3);

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

4. Замініть довгі списки позиційних параметрів об’єктом

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

createUser(
  "Shayan",
  "shayan@example.com",
  25,
  "developer",
  true,
  "Pakistan"
);

Передача одного об’єкта з іменованими властивостями робить кожне значення самоописним:

createUser({
  name: "Shayan",
  email: "shayan@example.com",
  age: 25,
  role: "developer",
  isActive: true,
  country: "Pakistan"
});

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

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

Стиснення логіки в один рядок може ускладнити її читання. У наведеному нижче рядку послідовно виконуються необов’язкові виклики через скорочені імена властивостей.

const result = users?.filter(u => u.a)?.map(u => u.n);

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

const activeUsers = users.filter(user => user.isActive);

Потім витягуємо їхні імена:

const activeUserNames = activeUsers.map(user => user.name);

Це коштує додаткового рядка, але читач розуміє його з першого погляду. Зверніть увагу на одну різницю в поведінці: у оригінальному коді використовувалося необов’язкове ланцюгове доступ, тому він допускав, щоб змінна users мала значення null або undefined, тоді як у переписаному варіанті це призводить до помилки. Якщо змінна users дійсно може бути відсутньою, необхідно прямо це обробити (наприклад, за допомогою значення за замовчуванням []) замість того, щоб приховувати це за допомогою ланцюга ?.. Керівним принципом є зробити код зрозумілим, а не вражаючим.

6. Плануйте на випадок невдачі, а не лише успішного сценарію

Реальні системи зламуються: API повертають помилки, користувачі надсилають некоректні дані, мережі втрачають зв’язок, бази даних виходять з ладу. Наведений нижче код передбачає, що все пройде успішно.

const response = await fetch("/api/users");
const users = await response.json();

Ключова деталь полягає у тому, що fetch відхиляє запит лише у разі проблем з мережею; статуси HTTP 404 чи 500 все одно обробляються. Удосконалена версія огортає запит у блок try:

try {
  const response = await fetch("/api/users");

потім перевіряє response.ok, кидає виняток у разі невдалого статусу, парсує тіло відповіді та ловить будь-які проблеми:

  if (!response.ok) {
    throw new Error("Failed to fetch users");
  }  const users = await response.json();  return users;
} catch (error) {
  console.error("Unable to load users:", error);
}

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

7. Коментуйте чому, а не що

Коментарі корисні; зайві є шумом. Цей коментар просто повторює те, що робить ця строка.

// Increment counter by 1
counter++;

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

// Delay the request to avoid triggering the API rate limit.
setTimeout(fetchUsers, 1000);

Корисне правило: нехай код описує, що він робить, а коментарі — чому він це робить.

8. Не включайте бізнес-правила до JSX

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

<div>
  {products
    .filter(product => product.stock > 0)
    .filter(product => product.price < 100)
    .map(product => (
      <ProductCard product={product} />
    ))}
</div>

Переміщення правила у змінну з іменем відокремлює те, що вважається доступним продуктом, від способу його відображення:

const availableProducts = products.filter(
  product => product.stock > 0 && product.price < 100
);

Тоді JSX описує лише інтерфейс:

<div>
  {availableProducts.map(product => (
    <ProductCard key={product.id} product={product} />
  ))}
</div>

Зверніть увагу, що перероблена версія також додає властивість key, якої бракувало в оригіналі, і яка необхідна React для правильного відстеження елементів списку.

9. Дозвольте інструментам забезпечувати послідовне форматування

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

const userName = "Shayan";

snake_case,

const user_name = "Shayan";

та PascalCase без пробілів чи крапки з комою:

const UserName="Shayan"

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

10. Пишіть так, щоб це було зрозуміле наступному адміністратору

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

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

Підсумок

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

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