Имена и маленькие функции
Что это такое
Имя — это интерфейс между кодом и человеком, который его читает, а функция — та единица, к которой имя прикреплено. Если отнестись к этим двум вещам серьёзно, всё остальное в чистом коде вытекает само: имя говорит, чем вещь является, а функция достаточно мала, чтобы имя честно описывало её целиком. И наоборот: когда функцию трудно назвать, это обычно означает, что она делает больше одной вещи.
Почему это важно
Большая часть времени, которое уходит на изменение, тратится на понимание того, как код ведёт себя сейчас, и плохие имена раздувают эту стоимость напрямую. Переменная data или temp заставляет читателя проследить, откуда она взялась и что в ней лежит, а функция process — прочесть всё тело целиком, чтобы выяснить, проверяет она данные, преобразует их или сохраняет. Переименование — самый дешёвый рефакторинг из существующих: если инструменты не врут, оно вообще ничего не может сломать, зато окупается при каждом последующем чтении.
Ключевые идеи
Имя должно говорить что, а не как
Выбирайте имя, которое называет смысл значения, а не его тип или происхождение. elapsedSeconds лучше, чем t, а activeSubscriptions — лучше, чем list. Длина тут не враг: длинное точное имя всякий раз выигрывает у короткого загадочного, а обычное соглашение — читать булевы значения как вопрос или предикат — превращает условия в предложения.
// непонятно
const d = users.filter((u) => u.a === 1);
// говорит о замысле
const activeUsers = users.filter((user) => user.status === "active");
Функция должна делать одну вещь
Проверка простая: опишите функцию одним предложением без «и». Если без «и» предложение не складывается, значит, внутри прячутся две функции. Речь не о количестве строк ради него самого — у маленькой функции мало движущихся частей, поэтому читатель держит в голове всю её разом, а имя сообщает остальное.
function invoiceTotal(lines) {
const subtotal = sumLines(lines);
const tax = computeTax(subtotal);
return subtotal + tax;
}
Опускайте детали вниз, держите верхний уровень читаемым
Функция, которая координирует работу остальных, должна читаться как сводка. Каждая её строка — один шаг того же уровня, а низкоуровневые детали уходят на вызов глубже: работа со строками, битовые флаги, неудобный API. Именно это и делает файл обозримым — читатель останавливается на нужном ему уровне, вместо того чтобы продираться сквозь детали, которые пока не нужны.
function importCsv(path) {
const rows = readRows(path);
const records = rows.map(parseRecord).filter(Boolean);
return saveRecords(records);
}
Держите число параметров небольшим
Каждый параметр — ещё одна вещь, которую вызывающий обязан угадать правильно, а длинный их список обычно означает, что функции нужен замысел, которому ещё не придумали имя. Два-три параметра удобны; дальше ищите значение, которое естественно группирует остальные, или делите функцию по настоящей границе. Отдельный случай — булевы параметры: они намекают, что за одним именем прячутся два поведения, поэтому таким функциям часто лучше разойтись на две.
// два поведения за одним флагом
render(record, true);
// по одному поведению каждое
renderSummary(record);
renderDetail(record);
Комментарии объясняют почему, а не что
Код уже говорит, что он делает. Комментарий заслуживает места только тогда, когда записывает то, чего код сказать не может: внешнее ограничение, обходной путь вместе с его причиной, решение, которое выглядит неверным, но принято намеренно. А комментарий, который пересказывает следующую строку или разошёлся с кодом, только добавляет шума и подрывает доверие читателя к файлу.
Практика
Переименуйте функцию, пока место вызова не станет читаться хорошо
Найдите функцию с самым неудачным именем в файле, который вы поддерживаете. Запишите место вызова английским предложением, а затем переименовывайте функцию и её параметры до тех пор, пока место вызова не начнёт читаться как это предложение.
Готово, когда: место вызова понятно без открытия самой функции, а изменение свелось к чистому переименованию, без правок поведения.
Разделите одну функцию на шаги
Возьмите функцию длиннее примерно двадцати строк и перепишите её как последовательность именованных шагов одного уровня, где каждый шаг — своя маленькая функция.
Готово, когда: исходная функция читается как сводка из трёх-четырёх вызовов, а её тесты по-прежнему проходят без изменений.
Частые ошибки
- Имя функции по тому, как она работает —
stringSplitAndJoinописывает механизм, а не назначение, поэтому вызывающему приходится её читать; исправить: назовите результат, который нужен вызывающему. - Длинная функция, чтобы избежать «слишком многих функций» — файл кажется опрятным, но читатель не держит в голове ничего; исправить: делите по шагам, а не по числу строк.
- Комментарий в починку плохого имени — имя так и остаётся неверным, а комментарий со временем расходится с кодом; исправить: сначала переименуйте и комментируйте только то, чего код сказать не может.
- Сокращения ради экономии набора —
usrMgrстоит читателю перевода при каждом появлении; исправить: пишите имя целиком, а набор оставьте автодополнению редактора. - Оставленный булев параметр — каждое место вызова читается как головоломка, а два пути стареют по-разному; исправить: разделите на две функции с именами, говорящими, какое поведение даёт каждая.
Что читать дальше
- Структура и обработка ошибок (01-cs-fundamentals/08-clean-code/02-structure-and-error-handling) — вторая половина привычки: порядок и пути отказа, когда куски уже малы.
- Переиспользование кода (01-cs-fundamentals/16-code-reusability) — как далеко заходить с выделением, прежде чем общий код станет обузой.
- Рефакторинг (01-cs-fundamentals/19-refactoring) — как безопасно делать эти переименования и разделения в чужом коде.