Главная / Статьи

Читаемый 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. Пишите так, чтобы это было понятно тем, кто будет поддерживать код дальше

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

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

Заключение

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

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