Имена и маленькие функции — Примеры

Пример 1 — Место вызова, которое никто не может прочесть

У функции ниже одно место вызова, и рецензент не может понять, что она делает, не прочитав тело. Скажите, какие две привычки она нарушает, затем перепишите функцию и вызов так, чтобы вызов читался как предложение.

function proc(u, a, f) {
  let r = [];
  for (const x of u) {
    if (x.s === "active") r.push(x);
  }
  if (f) r = r.slice(0, 10);
  if (a) r.sort((p, q) => q.c - p.c);
  return r;
}

const d = proc(users, true, false);

Решение

function activeUsersBySpend(users, limit) {
  return users
    .filter((user) => user.status === "active")
    .sort((a, b) => b.spend - a.spend)
    .slice(0, limit);
}

const topActiveUsers = activeUsersBySpend(users, 10);

Старые имена не описывают ничего: u, a и f загоняют читателя в тело функции, а два булевых флага заставляют её делать четыре вещи в зависимости от того, как их выставили. Стоит назвать результат и передать предел значением вместо флага — и место вызова само становится предложением, так что открывать функцию читателю больше не нужно. Ловушка здесь в другом: версию с флагом написать легче, ведь обе ветки уже есть, а добавление булева значения выглядит меньшим изменением, чем разделение функции.

Пример 2 — Функция, читающаяся как три функции

Тело ниже проверяет, преобразует и сохраняет в одном блоке. Скажите, как читатель может понять, что она делает больше одной вещи, затем перепишите её как шаги одного уровня.

async function handleUpload(file) {
  const text = await fs.readFile(file, "utf8");
  const lines = text.split("\n").filter((l) => l.trim() !== "");
  const rows = [];
  for (const line of lines) {
    const parts = line.split(",");
    if (parts.length < 3) continue;
    rows.push({ id: parts[0], name: parts[1], amount: Number(parts[2]) });
  }
  await db.insertMany(rows);
  return rows.length;
}

Решение

async function handleUpload(file) {
  const rows = await readRows(file);
  return saveRows(rows);
}

async function readRows(file) {
  const text = await fs.readFile(file, "utf8");
  return text.split("\n").map(parseRow).filter(Boolean);
}

async function saveRows(rows) {
  await db.insertMany(rows);
  return rows.length;
}

Функция-координатор теперь читается как сводка — два вызова одного уровня, — а детали лежат на шаг глубже, где parseRow держит разбиение и преобразование суммы. Ловушка в том, что исходный блок выглядит эффективным: один проход и никаких лишних имён, — но при этом заставляет читателя держать в голове разом чтение файла, разбор, проверку и сохранение. Рецензент даже не скажет, к какому из четырёх шагов относится следующий комментарий.