Именование — это акт проектирования
Имя сжимает намерение концепции в одно слово, поэтому акт именования заставляет тебя понять концепцию и очертить её границы — а значит, трудность именования это прямой сигнал о твоём дизайне, а не о словарном запасе.
Ты садишься выделить метод. Логика ясна, тесты проходят, осталось только назвать новую функцию — и ты застреваешь. processData? handleStuff? doWork? Каждый кандидат — либо ложь, либо пожимание плечами. Через десять минут у тебя есть имя, в которое ты не веришь, и смутное ощущение, что что-то не так с кодом, а не с тобой.
Что-то действительно не так с кодом. Трудность была не проблемой словарного запаса; это код говорил тебе, что у вещи, которую ты пытаешься назвать, нет единой ясной концепции за спиной. Именование — не последний косметический шаг после того, как дизайн готов. Именование и есть дизайн, проявляющийся в единственном месте, где его можно почувствовать.
После этого урока ты можешь объяснить, почему имя — это сжатие намерения и почему написать его значит очертить границы концепции; отличать имена, раскрывающие намерение (что/зачем), от имён, протекающих реализацией (как); читать трудность именования как обратную связь о дизайне — сигнал, что вещь делает слишком много; и избегать обратной ошибки — имён настолько переточённых, что они вваривают одноразовую деталь реализации в твой публичный словарь.
Имя — это сжатие намерения: оно хранит что и зачем, а не как. Читатель daysSinceLastLogin знает, что значение означает и зачем оно существует, не читая ни единой строки вычисления. Читатель d не знает ничего и вынужден каждый раз восстанавливать смысл из контекста. Имя — это интерфейс к концепции; тело — реализация. Хорошее имя позволяет читателю целиком пропустить тело на тех 95% прочтений, где ему нужен лишь смысл.
// имя несёт смысл; тело — деталь реализации
const d = Math.floor((Date.now() - user.lastLoginAt) / 86_400_000); // ✗ что такое d?
const daysSinceLastLogin = Math.floor((Date.now() - user.lastLoginAt) / DAY_MS); // ✓Краткая версия не быстрее в работе — она быстрее набирается и медленнее читается, а время уходит именно на чтение. Имя — самая дешёвая документация, которую ты когда-либо напишешь, потому что оно бесплатно путешествует со значением к каждому месту вызова.
Именование заставляет понять концепцию и очертить её границы. Чтобы назвать что-то, ты должен ответить на вопрос «что это за одна-единственная вещь?» — а это работа по проектированию, а не наклеивание ярлыка. Акт выбора chargeExpiredSubscriptions вместо process заставляет тебя решить: эта функция только списывает деньги, или ещё шлёт письма, логирует и делает повторы? Если она делает все четыре вещи, честное имя — это абзац, а абзац — это запах дизайна. Именование — самое дешёвое ревью дизайна, какое ты можешь провести, потому что оно идёт у тебя в голове ещё до коммита.
function process(x: Account[]) { /* ...80 строк... */ } // ✗ имя не раскрывает ничего
function chargeExpiredSubscriptions(accounts: Account[]) { } // ✓ имя — это контрактchargeExpiredSubscriptions — это обещание: оно списывает деньги, оно касается просроченных подписок, оно принимает аккаунты. Если тело потом начнёт ещё и продлевать подписки, имя начинает лгать — и эта ложь сигнал, что функция отрастила вторую обязанность.
Если ты не можешь назвать что-то чисто, оно, скорее всего, делает слишком много — или ты пока его не понимаешь. Классический признак — класс, который ты в итоге называешь UserManager, DataHelper или OrderService, потому что ничего более конкретного не подходит. Manager и Helper — не концепции; это признания, что коробка хранит всё, что не влезло в другие места. Тип, сопротивляющийся точному имени, сопротивляется ему именно потому, что у него нет единой ответственности — имя не может быть точным, потому что не точна сама вещь.
class UserManager {
validateEmail(e: string) {} // валидация
hashPassword(p: string) {} // криптография
sendWelcomeEmail(u: User) {} // уведомления
recordSignupMetric(u: User) {} // аналитика
}Ты не можешь назвать это лучше, чем UserManager, потому что внутри живут четыре несвязанные обязанности; расплывчатое имя — это симптом. Раздели его — EmailValidator, PasswordHasher, WelcomeMailer, SignupMetrics — и каждое имя становится очевидным в тот миг, когда коробка хранит одну идею. Трудность именования была обратной связью о дизайне; разделение — то, на что она указывала.
Переименование и трудность именования — инструменты проектирования, но переточённые имена — это их собственный режим отказа. Относись к застрявшему имени как к зонду: когда имя не садится, спроси, неясна концепция или раздута, и пусть ответ ведёт к разделению или переосмыслению. Но есть равная и противоположная ошибка: имена настолько конкретные, что вваривают деталь реализации, которую ты захочешь изменить, в публичный словарь.
// переточённое: имя обещает конкретный алгоритм и хранилище
function fetchUsersFromMySQLWithBTreeIndex(): User[] {} // ✗ вваривает «как» в имя
function findActiveUsers(): User[] {} // ✓ называет «что», прячет «как»fetchUsersFromMySQLWithBTreeIndex читается как точное имя, но оно прибило MySQL, тип индекса и «fetch» к каждому месту вызова. Перейди на Postgres или добавь кэш — и либо имя начинает лгать, либо ты переименовываешь по всей кодовой базе. Навык в том, чтобы называть стабильное намерение (findActiveUsers — то, чего хочет вызывающий) и держать изменчивое как (какая база, какой индекс) внутри тела, где оно свободно меняться. Точно про смысл; молчаливо про механизм.
Пусть трудность именования ведёт дизайн. Коллега написал это и попросил помочь с именем — он остановился на handle:
// как это назвать?
function handle(order: Order): void {
if (order.total > 0 && order.items.length > 0) { // валиден ли?
const tax = order.total * 0.2; // вычислить налог
order.finalTotal = order.total + tax; // мутировать заказ
db.save(order); // сохранить
mailer.send(order.customerEmail, "Order confirmed"); // уведомить
}
}Ты не можешь назвать это, потому что оно делает четыре вещи: валидирует, считает цену, сохраняет и уведомляет. handle и processOrder — оба пожимания плечами: они называют коробку, а не концепцию. Трудность именования — это дизайн, говорящий тебе раздели. Последуй ему, и каждый кусок назовёт себя сам:
function isPlaceable(order: Order): boolean {
return order.total > 0 && order.items.length > 0;
}
function withTax(order: Order): Order {
return { ...order, finalTotal: order.total + taxFor(order.total) };
}
function placeOrder(order: Order): void {
if (!isPlaceable(order)) return;
const priced = withTax(order);
orders.save(priced);
confirmation.send(priced);
}Теперь ни одно имя не трудно. isPlaceable, withTax, placeOrder несут ровно по одной идее, поэтому каждое имя очевидно — трудноназываемый handle никогда не был проблемой именования, это была неочерченная концепция в расплывчатом ярлыке. Заметь, что имена говорят что (placeOrder, withTax) и никогда как (multiplyByPointTwoAndSave); налоговая ставка и почтальон свободны меняться за ними.
▸Почему это работает
Почему трудность именования — такой надёжный сигнал о дизайне, когда «я просто плохо придумываю имена» кажется более простым объяснением? Потому что имя — это сжатие с потерями концепции, а сжатие с потерями работает только тогда, когда есть единственный доминирующий сигнал, который стоит сохранить. daysSinceLastLogin сжимается чисто, потому что идея одна. UserManager не сжимается, потому что четыре идеи борются за единственный слот, и ни одно слово не может представить четыре несвязанные вещи, не солгав. Так что имя, которое отказывается приходить, — это обычно не пробел в словаре, а концепция, говорящая тебе, что у неё больше одного центра тяжести. Трение — это информация. Потрать её, а не замажь словом Manager.
▸Частая ошибка
Соблазнительная обратная ошибка: гнаться за максимальной точностью и вваривать как в имя. getUserByIdFromRedisCacheElseMySQL, sortUsersWithQuicksort, EmailValidatorUsingRegex — каждое читается как строгое и каждое ловушка, потому что обещает механизм, о котором вызывающий не спрашивал и который ты в итоге поменяешь. Вызывающий хочет getUser, sortUsers, validateEmail; кэш, алгоритм и регулярка — приватные решения. Когда реализация меняется (а она поменяется), имя-намерение остаётся правдой, а имя-механизм становится ложью или переименованием по всей кодовой базе. Называй стабильное намерение; пусть изменчивая деталь живёт и умирает внутри тела. Переточённое — не «лишняя безопасность», это преждевременное обязательство под маской.
Ты выделяешь функцию и честно не можешь найти имя лучше, чем `handleOrderStuff`. Сквозь призму этого урока, о чём эта трудность скорее всего тебе говорит?
Имя — это сжатие намерения: оно хранит, что есть вещь и зачем она существует, чтобы читатели могли пропустить как. Поэтому написать хорошее имя — это работа по проектированию: оно заставляет тебя очертить одну концепцию, а трудность именования — это обратная связь о дизайне, а не пробел в словаре. Имя, которое не садится (Manager, Helper, process, handle), обычно означает, что вещь делает слишком много или пока не понята — лекарство в том, чтобы разделять или переосмыслять, пока каждая коробка не будет хранить одну идею и не назовёт себя сама. Симметричная ловушка — переточённые имена, прибивающие изменчивую деталь реализации (FromMySQL, UsingRegex, WithQuicksort) к публичному словарю; они читаются как строгие, но становятся ложью в тот миг, когда механизм меняется. Называй стабильное намерение, прячь изменчивый механизм — а когда имя сопротивляется тебе, относись к трению как к информации о дизайне, а не к провалу твоего словарного запаса.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.