Чистый код: зачем он нужен (и почему это не занудство)

Чистый код - это не «красота ради красоты». Это скорость.

Код читают в 10 раз чаще, чем пишут. И чаще всего его читаешь ты сам... через месяц, когда уже забыл, что хотел сказать автор (спойлер: автор - ты).

Что даёт чистый код

  • быстрее добавлять фичи - не нужно разбираться, что тут происходит
  • проще чинить баги - видно, где логика сломалась
  • легче тестировать - маленькие понятные функции покрываются за минуты
  • меньше страха трогать старые места
Цель - не сделать «идеально», а сделать **понятно** и **безопасно для изменений**. Иногда «достаточно хорошо» - это победа.

Стоимость грязного кода: технический долг

Грязный код - это технический долг (tech debt). Как и финансовый долг, он растёт с процентами:

Месяц 1:  «Потом поправлю, сейчас горит»
Месяц 3:  «Не трогай этот файл, он хрупкий»
Месяц 6:  «Новую фичу проще написать заново, чем встроить»
Месяц 12: «Нам нужен рефакторинг на 3 спринта»

В проектах с высоким tech debt разработчики тратят до 40% времени на борьбу с плохим кодом вместо создания ценности. Это не абстрактная проблема - это реальные часы, за которые платит команда.

Как понять, что код «грязный»

Есть шутливая, но точная метрика:

Качество кода = количество "WTF" за минуту при ревью

А есть измеримые показатели:

Метрика                       Что показывает
──────────────────────────     ─────────────────────────────────
Цикломатическая сложность     Сколько путей выполнения в функции
Когнитивная сложность         Насколько сложно человеку понять логику
Глубина вложенности           Сколько уровней if/for/switch
Длина функции                 Слишком длинная → делает слишком много
Покрытие тестами              Есть ли сеть безопасности

В Go для проверки есть golangci-lint - он считает десятки метрик за секунды:

// Цикломатическая сложность 1 - идеально
func IsActive(user User) bool {
    return user.Status == "active"
}

// Цикломатическая сложность 5 - уже стоит задуматься
func ProcessOrder(order Order) error {
    if order.User == nil { return ErrNoUser }
    if order.Items == nil { return ErrNoItems }
    for _, item := range order.Items {
        if item.Price <= 0 { return ErrInvalidPrice }
        if item.Qty <= 0 { return ErrInvalidQty }
    }
    return nil
}
<?php
declare(strict_types=1);

// Цикломатическая сложность 1 - идеально
final readonly class UserStatus
{
    public static function isActive(User $user): bool
    {
        return $user->status === 'active';
    }
}

// Цикломатическая сложность 5 - уже стоит задуматься
final readonly class OrderProcessor
{
    public function process(Order $order): void
    {
        if ($order->user === null) {
            throw new \DomainException('order has no user');
        }
        if ($order->items === []) {
            throw new \DomainException('order has no items');
        }
        foreach ($order->items as $item) {
            if ($item->price <= 0) {
                throw new \DomainException('invalid item price');
            }
            if ($item->qty <= 0) {
                throw new \DomainException('invalid item qty');
            }
        }
    }
}

В PHP сложность измеряет phpmd (правило CyclomaticComplexity) или phpstan через phpstan/phpstan-strict-rules. Symfony-проекты часто настраивают порог через phpmd ruleset.xml в CI - аналог golangci-lint для Go.

Правило бойскаута

Роберт Мартин (автор «Чистого кода») предложил простое правило:

Оставь код чище, чем нашёл.

Не нужно переписывать весь файл. Достаточно каждый раз делать одно маленькое улучшение:

  • переименовал непонятную переменную
  • вынес дублирующийся блок в функцию
  • добавил контекст к ошибке
  • удалил мёртвый комментарий
Без правила бойскаута:          С правилом бойскаута:
Код деградирует с каждым PR     Код улучшается с каждым PR
─────────────────────────────   ─────────────────────────────
Sprint 1:  ████████░░  80%      Sprint 1:  ████████░░  80%
Sprint 5:  ██████░░░░  60%      Sprint 5:  █████████░  85%
Sprint 10: ████░░░░░░  40%      Sprint 10: █████████░  90%
Не путай с большим рефакторингом. Правило бойскаута - это **микроулучшения** при каждом касании файла. Если ты открыл файл для фикса бага - переименуй одну непонятную переменную заодно. Не больше.

Самая частая боль новичка

«Я боюсь что-то менять, потому что сломаю всё».

Чистый код уменьшает этот страх: изменения становятся маленькими и предсказуемыми. Но есть и второй компонент - тесты (Go, PHP). Чистый код без тестов - как ремень безопасности без подушки: лучше, чем ничего, но защита неполная.

Чистый код + тесты  → «Меняю уверенно, тесты покажут если сломал»
Чистый код - тесты  → «Выглядит понятно, но менять всё равно страшно»
Грязный код + тесты → «Тесты зелёные, но что именно они тестируют?»
Грязный код - тесты → «Не трогай, работает»

Чистый код в Go: культура языка

Go как язык поощряет чистый код на уровне дизайна:

  • gofmt - единый формат, споры о стиле исключены
  • go vet - статический анализ ошибок
  • Короткие имена для коротких скоупов, длинные - для широких
  • Ошибки как значения - нельзя «случайно» проигнорировать
  • Неявные интерфейсы - меньше связности между пакетами

Если ты пишешь на Go и следуешь конвенциям языка - ты уже на полпути к чистому коду.

Пять принципов чистого кода

Этот трек построен вокруг пяти ключевых идей:

1. Понятные имена        → урок 2
2. Маленькие функции     → урок 3
3. Плоская структура     → урок 4 (ранние return)
4. Явные ошибки          → урок 5
5. Осмысленная структура → урок 7

Каждый принцип - маленький шаг. Вместе они дают код, который приятно читать и безопасно менять.

Мини-задание

  • Найди в своём проекте файл, который страшно открывать
  • Посчитай максимальную глубину вложенности (сколько уровней if/for)
  • Примени правило бойскаута: сделай одно маленькое улучшение и закоммить
  • Запусти golangci-lint run ./... и посмотри, какие метрики он покажет

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