Learn JavaScript

10 правил понятного JS-кода: чек-лист

Понятный код - это код, который легко читать, менять и отлаживать. Ниже - чек-лист из 10 правил. Их удобно держать в голове при написании и ревью.

1) Имена должны отвечать на вопрос “что это” и “в каких единицах”

Плохо:

const x = 300;
const d = 5;

Лучше:

const timeoutMs = 300;
const daysCount = 5;

Если в проекте договоренность про единицы (Ms, Sec, Count) - код становится предсказуемее.

2) Одна функция - одна задача

Плохо: функция делает загрузку, преобразование и рендер.

async function loadAndRender() {
  const response = await fetch("/api/items");
  const items = await response.json();

  const active = items.filter((x) => x.active);
  document.querySelector("#out").textContent = String(active.length);
}

Лучше: отдельные функции по смыслу.

async function loadItems() {
  const response = await fetch("/api/items");

  if (!response.ok) {
    throw new Error("HTTP " + response.status);
  }

  return response.json();
}

function countActive(items) {
  return items.filter((x) => x.active).length;
}

function renderCount(root, value) {
  root.textContent = String(value);
}

3) Ранний выход делает условия проще

Плохо: много вложенности.

function saveUser(user) {
  if (user) {
    if (user.name) {
      return "ok";
    } else {
      return "no name";
    }
  } else {
    return "no user";
  }
}

Лучше: guard clauses.

function saveUser(user) {
  if (!user) return "no user";
  if (!user.name) return "no name";
  return "ok";
}

4) Предсказуемые сравнения: === и явные проверки

Плохо: полагаться на приведение типов.

if (value == 0) {
  // ...
}

Лучше: явное сравнение.

if (value === 0) {
  // ...
}

Если ожидается число, лучше привести заранее.

const n = Number(value);
if (Number.isNaN(n)) return;
if (n === 0) return;

5) Сложные условия выносить в переменные с именами

Плохо: условие трудно читать.

if (user && user.role === "admin" && user.isActive && user.age >= 18) {
  // ...
}

Лучше: читаемые части.

const isAdmin = user && user.role === "admin";
const isAllowedAge = user && user.age >= 18;

if (isAdmin && user.isActive && isAllowedAge) {
  // ...
}

6) Не смешивать данные и представление

Плохо: функция логики возвращает HTML.

function buildHtml(items) {
  return items.map((x) => "<li>" + x.title + "</li>").join("");
}

Лучше: логика возвращает данные, UI рисует отдельно.

function getTitles(items) {
  return items.map((x) => x.title);
}

function renderList(root, titles) {
  root.innerHTML = "";
  for (const title of titles) {
    const li = document.createElement("li");
    li.textContent = title;
    root.append(li);
  }
}

7) Минимизировать мутации и побочные эффекты

Плохо: функция меняет входные данные неожиданно.

function sortNumbers(nums) {
  return nums.sort((a, b) => a - b);
}

Лучше: копировать.

function sortNumbers(nums) {
  return [...nums].sort((a, b) => a - b);
}

Побочные эффекты (DOM, сеть, localStorage) лучше держать в одном месте, а не “размазать” по всем файлам.

8) Делать “контракт” функции очевидным

Если функция ожидает объект определенной формы, это должно быть видно по коду.

Плохо:

function print(user) {
  return user.name.toUpperCase();
}

Лучше: проверка входных данных или понятное имя.

function formatUserName(user) {
  if (!user || typeof user.name !== "string") return "";
  return user.name.toUpperCase();
}

9) Ошибки и ветки “неуспеха” должны быть видны

Для async-кода удобно:

  • проверять response.ok
  • выбрасывать ошибку с понятным сообщением
  • ловить ее на верхнем уровне
async function loadJSON(url) {
  const response = await fetch(url);

  if (!response.ok) {
    throw new Error("HTTP " + response.status);
  }

  return response.json();
}

async function run() {
  try {
    const data = await loadJSON("/api/data");
    console.log(data);
  } catch (e) {
    console.log(e.message);
  }
}

run();

10) Единый стиль: отступы, скобки, длина строк

Даже базовые договоренности делают код легче:

  • одинаковые отступы (обычно 2 или 4 пробела)
  • фигурные скобки в одном стиле
  • одинаковые кавычки (например, только ”)
  • понятные переносы строк, без “простыней” условий

Пример: лучше переносить цепочки и сложные вызовы.

const result = items
  .filter((x) => x.active)
  .map((x) => x.title);

Быстрый чек-лист перед коммитом

  • Имена переменных понятны и не слишком короткие.
  • Функции короткие и делают одну вещь.
  • Вложенные if сведены к ранним выходам.
  • === используется по умолчанию.
  • Сложные условия вынесены в переменные.
  • Логика не содержит DOM, UI не содержит правила.
  • Мутации массивов/объектов осознанны или есть копии.
  • Входные данные проверяются там, где это нужно.
  • Ошибки обрабатываются и дают понятный сигнал.
  • Стиль кода единый по всему проекту.