Имена в коде: переменные, функции, файлы

Имена в коде: переменные, функции, файлы

Имя - это комментарий, который не устаревает (если ты не врёшь в имени).

Мы тратим больше времени на чтение кода, чем на его написание. Хорошие имена - самый дешёвый способ сделать код понятнее.

Плохие vs хорошие имена

Плохие имена d, calc, Manager против хороших daysToExpire, calculateInvoiceTotal, UserRepository

<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-интерфейсах.

Чем шире область видимости, тем длиннее имя. `i` в цикле - нормально. `i` на уровне пакета - ненормально. Exported-функция `G()` - плохо, `GetUserByEmail()` - хорошо.

Антипаттерны именования

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 - оно проверяет конвенции именования

Зарегистрируйтесь бесплатно, чтобы пройти квиз, решить задание с автопроверкой и вести прогресс.