Имена в коде: переменные, функции, файлы
Имена в коде: переменные, функции, файлы
Имя - это комментарий, который не устаревает (если ты не врёшь в имени).
Мы тратим больше времени на чтение кода, чем на его написание. Хорошие имена - самый дешёвый способ сделать код понятнее.
Плохие vs хорошие имена
<ComparisonTable data={ headers: ["Плохо", "Почему", "Лучше"], rows: [ ["data", "что за data?", "users / tasks / config"], ["x, y, z", "неясно", "total, count, age"], ["doStuff()", "что именно?", "sendEmail(), calculateTotal()"], ["user1", "почему 1?", "currentUser / author"], ["flag", "какой флаг?", "isActive / hasPermission"], ["list", "список чего?", "pendingOrders / failedJobs"] ], } />
Правило «без контекста должно быть понятно»
Открой функцию отдельно - должно быть ясно, что она делает. Если нужно прочитать 50 строк выше, чтобы понять result - имя плохое.
// Плохо: что за d? что за result?
func process(d []byte) ([]byte, error) {
result, err := transform(d)
return result, err
}
// Хорошо: читается как предложение
func compressImage(raw []byte) ([]byte, error) {
compressed, err := gzip.Encode(raw)
return compressed, err
}
<?php
declare(strict_types=1);
// Плохо: что за $d? что за $result?
function process(string $d): string
{
return transform($d);
}
// Хорошо: читается как предложение
function compressImage(string $raw): string
{
return gzencode($raw);
}
Конвенции именования в Go
Go имеет строгие конвенции, закреплённые в Effective Go и Go Code Review Comments (см. также урок про интерфейсы - у них особое правило именования через суффикс -er):
Правило Пример
───────────────────────────────── ────────────────────────────
Exported = с большой буквы User, GetByID, ErrNotFound
Unexported = с маленькой userRepo, getByID, errTimeout
Акронимы целиком в одном регистре userID (не userId), httpClient
Однобуквенные - только в коротких for i, v := range items { ... }
циклах и замыканиях
Интерфейсы с суффиксом -er Reader, Writer, Closer, Stringer
Пакеты - одно слово, lowercase http, json, auth (не httpUtils)
Конвенции именования в PHP (PSR-12)
PHP-сообщество стандартизировало именование через PSR-1/PSR-12. Symfony, Laravel, PHPUnit - все следуют этим правилам:
Правило Пример
───────────────────────────────── ────────────────────────────
Класс PascalCase UserRepository, OrderService
Метод camelCase getByEmail(), placeOrder()
Свойство camelCase $userName, $isActive
Константа SCREAMING_SNAKE MAX_RETRIES, DEFAULT_TTL
Интерфейс ...Interface CacheInterface, LoggerInterface
Trait ...Trait TimestampableTrait
Abstract class Abstract... AbstractController
Exception ...Exception UserNotFoundException
Namespace PascalCase App\Domain\User
Файл = имя класса User.php с одним классом внутри
В Symfony дополнительно: репозитории называются XxxRepository, контроллеры XxxController, события XxxEvent, команды XxxCommand - суффикс несёт смысл, как -er в Go-интерфейсах.
Антипаттерны именования
1. Имена-отмазки
// temp, newData, result2 - «я не знаю, как назвать»
temp := fetchUsers()
newData := transform(temp)
result2 := save(newData)
// Лучше: каждая переменная говорит, что в ней
users := fetchUsers()
enrichedUsers := transform(users)
savedCount := save(enrichedUsers)
<?php
declare(strict_types=1);
// $temp, $newData, $result2 - «я не знаю, как назвать»
$temp = fetchUsers();
$newData = transform($temp);
$result2 = save($newData);
// Лучше: каждая переменная говорит, что в ней
$users = fetchUsers();
$enrichedUsers = transform($users);
$savedCount = save($enrichedUsers);
2. Избыточный контекст
// Плохо: «user» повторяется в каждом поле
type User struct {
UserName string
UserEmail string
UserAge int
}
// Хорошо: контекст уже в имени типа
type User struct {
Name string
Email string
Age int
}
<?php
declare(strict_types=1);
// Плохо: 'user' повторяется в каждом поле
final readonly class User
{
public function __construct(
public string $userName,
public string $userEmail,
public int $userAge,
) {}
}
// Хорошо: контекст уже в имени класса
final readonly class User
{
public function __construct(
public string $name,
public string $email,
public int $age,
) {}
}
3. Отрицание в булевых именах
// Плохо: двойное отрицание ломает мозг
if !isNotActive { ... }
// Хорошо: положительное имя
if isActive { ... }
<?php
declare(strict_types=1);
// Плохо: двойное отрицание ломает мозг
if (!$isNotActive) { /* ... */ }
// Хорошо: положительное имя
if ($isActive) { /* ... */ }
4. Общие глаголы
// Плохо: handle, process, manage - ничего не говорят
func handleData(d Data) error { ... }
// Хорошо: конкретное действие
func validateOrder(order Order) error { ... }
func enrichUserProfile(user User) (User, error) { ... }
<?php
declare(strict_types=1);
// Плохо: handle, process, manage - ничего не говорят
final class DataHandler
{
public function handleData(Data $d): void { /* ... */ }
}
// Хорошо: конкретное действие в имени класса И метода
final class OrderValidator
{
public function validate(Order $order): void { /* ... */ }
}
final class UserProfileEnricher
{
public function enrich(User $user): User { /* ... */ }
}
Рефакторинг имён: пошаговый пример
Допустим, ты видишь такой код:
func do(c context.Context, id int) (*T, error) {
r, err := s.Get(c, id)
if err != nil {
return nil, err
}
if r.St != 1 {
return nil, errors.New("bad")
}
return r, nil
}
Шаг за шагом:
// 1. Функцию: do → GetActiveTask
// 2. Параметры: c → ctx (стандарт Go), id → taskID
// 3. Переменные: r → task, s → taskRepo
// 4. Магическое число: St != 1 → Status != StatusActive
// 5. Ошибку: "bad" → "task is not active"
func GetActiveTask(ctx context.Context, taskID int) (*Task, error) {
task, err := taskRepo.Get(ctx, taskID)
if err != nil {
return nil, fmt.Errorf("get task %d: %w", taskID, err)
}
if task.Status != StatusActive {
return nil, fmt.Errorf("task %d is not active (status=%s)",
taskID, task.Status)
}
return task, nil
}
Тот же рефакторинг в PHP:
<?php
declare(strict_types=1);
// Плохо
final class S
{
public function do(int $id): ?T
{
$r = $this->get($id);
if ($r === null) {
return null;
}
if ($r->st !== 1) {
throw new \Exception('bad');
}
return $r;
}
}
// Хорошо: переименование на каждом уровне
final readonly class TaskRepository
{
public function getActiveTask(int $taskId): Task
{
$task = $this->taskRepo->findById($taskId);
if ($task === null) {
throw new TaskNotFoundException(sprintf('task %d not found', $taskId));
}
if ($task->status !== TaskStatus::Active) {
throw new TaskNotActiveException(
sprintf('task %d is not active (status=%s)', $taskId, $task->status->value),
);
}
return $task;
}
}
Каждый шаг - безопасный. Каждый шаг - улучшение. IDE подсвечивает все использования при переименовании.
Именование в разных слоях
Слой Конвенция Пример
──────────── ─────────────────────────── ──────────────────────────
Domain Бизнес-термины Order, LineItem, Discount
Repository Get/Create/Update/Delete GetByID, CreateOrder
Use Case Действие + объект PlaceOrder, CancelSubscription
Handler HTTP-метод + ресурс handleGetUser, handleCreateOrder
Когда имена согласованы между слоями, код читается как документация.
Мини-задание
- Возьми 10 переменных в проекте и переименуй так, чтобы они читались как текст
- Найди все
temp,data,result- замени на осмысленные имена - Проверь: exported-функции имеют ясные имена? Интерфейсы заканчиваются на
-er? - Запусти
golangci-lintс правиломrevive- оно проверяет конвенции именования