Иногда достаточно открыть структуру проекта, чтобы многое понять о команде. И не в лучшую сторону. Например, можно встретить такие каталоги:
project
├─ old_config
├─ mordor
├─ BlackMagic
└─ ...Причин для их появления масса. Кто-то просто скопировал старую директорию. Кто-то решил временно «вынести в сторону» непонятный или страшный код. Но так и не разобрался, что с ним делать. А потом это «временно» прижилось и стало частью архитектуры. Это симптом того, что именованию в проекте никто не уделяет внимания, а ведь имена — это первая линия коммуникации.
Теперь представьте, что вы открыли чужой код и наткнулись на переменные:
$pogoda;
$veter;
$solnce;Такие имена мгновенно выдают новичка.
Это напоминает, как я писал СМС-сообщения в нулевых: тогда сообщения имели ограничение по количеству символов, а на латинской раскладке в одно сообщение помещалось намного больше текста, чем при использовании кириллицы. Поэтому, если не удавалось уложиться в лимит, я писал транслитом:
Privet. Mi segodnya vtretimsya v parke?
Ya vzal s soboy...
Это была вынужденная мера, но та эпоха давно закончилась. В программном коде большинство фреймворков, библиотек, документаций — на английском. Если в коде появляется нечто вроде:
class Order extends Controller
{
public function ...()
{
// ...
foreach ($zakazy as $tovar) {
$product->otpravka($tovar);
}
// ...
}
}Создаётся разрыв контекста, фреймворк говорит на одном языке, твой код — на другом. Переключаться между языками утомительно, особенно в больших проектах. Это снижает читаемость и замедляет понимание.
Если имена переменных, файлов, классов, папок не передают смысл — они становятся ментальным мусором. Чем их больше — тем труднее читать, понимать и поддерживать код.
Поэтому нужно заботиться об именовании. Но что делает имена хорошими? И как начать исправлять это прямо сейчас?
Некоторым разработчикам нравится использовать сокращения в именах, что кажется для них удобным и помогает ускорить написание кода. Некоторые языки даже рекомендуют подобный подход — например, в языке Go советуют:
Имя объекта, для которого вызывается метод, должно отражать его суть; часто достаточно одной или двух букв, обозначающих тип (например, «c» или «cl» для «Client»). Не используйте общие имена вроде «me», «this» или «self».
Сокращения могут быть как однобуквенными, так и более длинными или смешанными, например, итерация цикла как $i, запрос
как q, интерфейс как IComponent.
Однако зачастую подобные сокращения лишь приводят к путанице и усложняют поддержку кода.
Это происходит потому, что, взяв небольшой фрагмент кода, невозможно сразу понять, что происходит. Разработчику приходится либо возвращаться к месту объявления переменной, чтобы разобраться, либо открывать исходники метода или класса. В итоге такой код начинает выглядеть как шифровка: смотришь на него — не понимаешь, что значит каждая переменная. Потом приходится искать «ключ», чтобы расшифровать смысл, и так по кругу. Но мы ведь не должны заниматься разведкой.
Давайте рассмотрим следующий пример:
// Плохо [✗]
$usr = User::find($id);
// Хорошо [✓]
$user = User::find($id);Здесь переменная $usr представляет объект пользователя. Однако, сокращённое имя $usr не даёт понимания того, что
именно хранится в этой переменной.
// Плохо [✗]
class UsrCtrl extends Ctrl {
public function f() {
// ...
}
}В данном примере имя класса UsrCtrl недостаточно информативно. Разработчику, сталкивающемуся с этим классом впервые,
будет трудно понять его назначение. Название класса должно чётко отражать его функциональность, например,
ProfileController.
// Хорошо [✓]
class ProfileController extends Controller
{
public function show()
{
// ...
}
}В некоторых книгах по программированию можно встретить утверждения, что код должен быть самодокументируемым. Это значит, что имена переменных, методов и классов должны объяснять, что происходит — без комментариев, без документации, без менторов.
Некоторые даже добавляют:
«Пусть имя будет длинным — это сделает код самодокументированным и более понятным».
Нет. Не делает. Особенно если это имя — дымовая завеса над тем, что в коде нет ни логики, ни смысла.
Я встречал разработчиков, воспитанных на строгих правилах: «Имена должны быть максимально подробными, чтобы не нужны были комментарии».
В теории звучит благородно, но на практике часто рождает чудовищные конструкции вроде:
public function retrieveUserAccountByEmailAddress(
string $email
): ?UserAccountДа, здесь всё предельно описательно. Но вместе с этим — чрезмерно длинно, тяжело читается и мешает восприятию кода. А ещё хуже, когда длинные имена тратят всю эту длину не на конкретику, а на расплывчатые абстракции:
abstract class AbstractContextHandler
{
use SemanticMapper;
public string $moduleScopeIdentifier = 'reporting';
public function process(
array $contextualizedComponentUnitPayload
): array
{
$moduleScopedUnits = [];
foreach ($contextualizedComponentUnitPayload as $contextBoundSemanticUnit) {
$moduleScopedResponseUnits[] = $this->transformContextUnit($contextBoundSemanticUnit);
}
return $moduleScopedResponseUnits;
}
protected function transformContextUnit(
$contextBoundSemanticUnit
): array
{
return [
'encodedPayloadFragment' => $this->map($contextBoundSemanticUnit),
'operationalModuleDomain' => $this->moduleScopeIdentifier,
];
}
}Что делает этот класс? Не ясно. Что он обрабатывает? Какой «контекст»? Какой «модуль»? Что за «единицы компонентов»?
Это типичный корпоративный анти-паттерн: взять простую задачу, обернуть её в кучу терминов, и сделать вид, что это архитектура.
Этот код невозможно понять. Не потому что он глупый. А потому что этот код никогда ничего конкретного не делал.
Предыдущий пример лишён конкретики, но она может отсутствовать и в именах переменных вроде таких:
// Плохо [✗]
$data;
$var;
$info;
$item;На первый взгляд выглядят нормально. И правда, в крошечных методах, где весь контекст на виду, такие имена вполне читаемы. Но стоит методу хоть немного разрастись — и смысл переменной начинает размываться.
Что именно скрывается за $data? Это может быть пользователь, список заказов, JSON или какая-нибудь внутренняя
структура.
Мы не знаем, пока не полезем внутрь: смотреть, что туда присваивается, как используется, что откуда приходит.
А даже если разберёмся — не факт, что в другом месте кода $data не означает уже совсем другое.
Это относится и к методам, иногда вполне нормально иметь метод run для классов, которые выполняют одну единственную
функцию (так называемые action‑классы).
Но это совершенно не информативно для масштабных объектов, например:
// Плохо [✗]
$user->run();
$user->handleData();
$user->process();Старайтесь использовать информативные имена, которые отражают суть того, что они представляют, например:
// Хорошо [✓]
$user->posts();
$user->notify(...);
$user->deactivate();Переменные и методы, которые содержат логическое значение (true или false), часто называют непонятно. Например:
// Плохо [✗]
$admin;
$retry;
$user->access();В этих примерах трудно понять назначение переменной или метода: $admin может быть флагом или объектом пользователя,
$retry — числом попыток или логическим состоянием, а метод access() — проверкой доступа или действием. Чтобы сразу
было
понятно, что значение логическое, используют префиксы is, has и should:
// Хорошо [✓]
$isAdmin = true;
$shouldRetry = false;
$user->hasAccess();Рассмотрим пример именования переменных с указанием единиц измерения температуры:
// Плохо [✗]
$temperature = 98.6;На первый взгляд всё выглядит нормально — просто число. Но что это за температура? Фаренгейты? Градусы Цельсия? Кельвины?
Ситуация усложняется, если в другом месте кода встречается:
$temperature = 37;Чтобы избежать путаницы, можно явно указывать единицы измерения:
// Хорошо [✓]
// Мы явно указываем, что это температура в фаренгейтах
$temperatureInFahrenheit = 98.6;
// Хорошо [✓]
// Или в градусах Цельсия
$temperatureInCelsius = 37;Другой способ справиться с этим — создать специальные объекты.
Создадим объект Temperature со статическими конструкторами, каждый из которых явно указывает единицу измерения:
class Temperature
{
public static function fromCelsius(float $degrees): self
{
return new self($degrees);
}
public static function fromFahrenheit(float $degrees): self
{
$celsius = self::convertFahrenheitToCelsius($degrees);
return new self($celsius);
}
private function __construct(
public float $valueInCelsius,
) {}
}Использование класса Temperature поясняет, что ожидается:
// Хорошо [✓]
$temperature = Temperature::fromFahrenheit(98.6); // 37.0°C
$temperature = Temperature::fromCelsius(37.0); // 37.0°CТаким образом, единица измерения становится несущественной — объект скрывает детали и позволяет получить значение в нужном формате.
Не стоит пытаться расписать всё длинными именами в надежде, что это сделает код понятнее. Вместо этого давайте ровно столько информации, чтобы можно было уверенно принимать решения. А всё лишнее — уберите.
Рассмотрим пример:
// Плохо [✗]
class PostItemCollection
{
public function addPost(Post $post)
{
// Добавляем пост в коллекцию
}
public function hasPost(Post $post): bool
{
// Проверяем, есть ли пост в коллекции
}
public function clearPost()
{
// Очищаем коллекцию
}
}Здесь много повторов и избыточных уточнений в именах методов.
Ведь всё понятно из контекста класса — это коллекция постов.
При этом слово Item в имени класса ничего не добавляет и скорее мешает.
Поэтому лучше упростить:
// Хорошо [✓]
class PostCollection
{
public function add(Post $post)
{
// Добавляем пост в коллекцию
}
public function has(Post $post): bool
{
// Проверяем, есть ли пост в коллекции
}
public function clear()
{
// Очищаем коллекцию
}
}Теперь вместо длинных имён — простой класс с тремя понятными методами: add, has и clear.
Каждый делает ровно то, что ожидаешь, без лишних слов.
Такой подход помогает избежать длинных и сложных имён, при этом сохраняя ясность и понятность. Код становится чище, короче и проще для восприятия.
В мире объектно-ориентированного программирования слишком часто встречаются имена классов вроде:
- Manager
- Controller
- Formatter
- Presenter
Эти имена плохи не потому, что они технически неверны. Они плохи потому, что не говорят ничего конкретного, слишком абстрактны.
Такая абстракция хороша для высокоуровневых концепций типа фреймворков, но не для конкретных классов в вашем приложении.
А ведь имя класса — это первый и, зачастую, единственный источник информации о его ответственности.
Эти имена — дымовая завеса. Они скрывают детали, замыливают смысл, делают код нечитаемым, а архитектуру — расплывчатой. Они подменяют суть интерфейсом, упрощая названия до абсурда. Да, это удобно. Да, так делают все. Но именно поэтому ваш проект через год превращается в груду мусора.
// Плохо [✗]
class ReportManager { /* … */ }
class StringFormatter { /* … */ }// Хорошо [✓]
class StringTruncatedToLength { /* … */ }Если вы не можете придумать конкретное имя — это сигнал, что саму ответственность объекта стоит пересмотреть.
Методы в паре работают лучше, когда звучат как единое целое. Они напоминают диалог: начало перекликается с концом.
Рассмотрим пример, где имена не согласованы:
// Плохо [✗]
$object->startProcess();
$object->completeTask();Здесь методы словно из разных рассказов — они не складываются в цельный образ. Такой код заставляет остановиться и задуматься, как эти действия связаны между собой. Это — явный признак слабого дизайна и плохой коммуникации через код.
Гораздо эффективнее, когда методы звучат как пара, поддерживают одну мысль и логически соответствуют друг другу:
// Хорошо [✓]
$object->startProcess();
$object->finishProcess();Или так:
$object->beginTask();
$object->completeTask();Тогда логика воспринимается как диалог — вызов и ответ. Такой подход существенно облегчает понимание кода: у читателя возникает естественный и логичный поток мысли.
Но чтобы такой «диалог» методов работал ещё лучше, класс должен быть сфокусирован на одной задаче или сущности. Например:
// Хорошо [✓]
$task->begin();
$task->complete();// Хорошо [✓]
$task->start();
$task->finish();Здесь класс чётко определяет свою зону ответственности — работу с задачей. Методы — естественные этапы жизненного цикла этой задачи. Такой фокус значительно упрощает тестирование, поддержку и развитие кода.
Мы верим, что метод делает то, что обещает его имя. Мы верим, что класс отражает свою роль. Мы верим, что код говорит правду. Но стоит нарушить это правило — и доверие рушится. Имя, которое вводит в заблуждение, оставляет ощущение предательства: разработчик ожидал одно поведение, а получил другое. После этого каждый следующий метод он будет читать уже с подозрением.
Пример метода с вводящим в заблуждение названием:
// Плохо [✗]
public function saveModels(array $item): void
{
$model = new Model();
$model->setAttributes($item);
// ...
}Метод заявляет, что он сохраняет модели — во множественном числе, и принимает массив. Если не заглядывать внутрь, любой прочитавший его разработчик подумает, что использовать его можно примерно так:
$models = [
new Model(),
new Model(),
new Model(),
];
$object->saveModels($models);Но вместо этого — разочарование от предательства. Метод берёт массив, который на самом деле описывает атрибуты одной модели. Название подтолкнуло к ложной ментальной модели. Название обещало одно действие, а сделало совершенно другое.
Это подрывает базовый инструмент командной работы — язык. Когда названия перестают соответствовать коду, разработчики очень быстро теряют к нему доверие, начинают бояться ошибиться и реже вносят изменения.
Имена — это не только классы, объекты и методы. Они затрагивают всё вокруг. Например, в веб-приложении важно выбрать понятные имена для адресов:
https://example.com/WeatherReport
https://example.com/weather_report
https://example.com/weather-report
То же касается ключей в JSON-переводах:
{
"WeatherReport": "Отчёт о погоде",
"weather_report": "Отчёт о погоде",
"weather-report": "Отчёт о погоде"
}Или файлов и директорий, не связанных напрямую с языком или фреймворком:
project
├─ WeatherReports
├─ weather_reports
├─ weather-reports
└─ ...Чтобы всё оставалось предсказуемым, выработайте в команде своё соглашение и используйте его там, где общие правила не помогают.