← назад к разделу

Код читают чаще, чем пишут: одну строчку напишут раз, а перечитают её при каждой правке рядом — свои же и коллеги, через полгода и без контекста. Clean Code — набор привычек Роберта Мартина о том, как писать так, чтобы этот второй читатель понял код без археологических раскопок. Это не про красоту ради красоты, а про стоимость изменений: чем понятнее код, тем дешевле его менять.

Разберём главные привычки — не как догму, а как ответы на конкретную боль «почему я не понимаю, что тут происходит».

Имена, которые не надо расшифровывать

Имя — самый частый комментарий в коде. Хорошее имя объясняет смысл без пояснений:

// было — что такое d? в днях? в чём?
int d = (now - created) / 86400;

// стало — имя отвечает на вопрос само
int daysSinceRegistration = (now - created) / SECONDS_IN_DAY;

Несколько правил, которые сразу дают эффект:

  • Имя раскрывает намерение. listactiveUsers, flagisEmailConfirmed, dataorderPayload.
  • Без загадочных сокращений. calcTot() экономит четыре буквы и стоит секунды на каждое чтение; calculateTotal() — не стоит.
  • Длина под область видимости. Счётчик в трёхстрочном цикле может быть i; поле класса, живущее по всему модулю, — нет.
  • Единый словарь. Если в одном месте user, в другом customer, а в третьем client — читатель гадает, это одно и то же или нет. Договоритесь об одном термине (это и есть Ubiquitous Language из DDD).

Функция делает одно

Главное правило про функции: одна функция — одно дело, на одном уровне абстракции. Функция, которая и достала данные, и посчитала, и отправила письмо, — три функции, слипшиеся в одну.

// делает слишком много — читать надо целиком, тестировать тяжело
void handleOrder(Order o) {
    // валидация, расчёт скидки, запись в БД, отправка письма — всё вперемешку
}

// каждая делает одно; handleOrder читается как оглавление
void handleOrder(Order o) {
    validate(o);
    applyDiscount(o);
    orderRepo.save(o);
    notifier.sendConfirmation(o);
}

Признаки, что функцию пора делить: она не помещается на экран, у неё несколько уровней вложенности if, вам приходится писать комментарий-заголовок вроде // теперь считаем скидку — этот комментарий и есть имя будущей функции.

Отдельно про флаги-аргументы: send(message, true) — что значит true? Булев параметр обычно означает, что функция делает две разные вещи; чаще честнее две функции — sendNow(message) и scheduleSend(message).

Комментарии: почему, а не что

Хороший комментарий объясняет то, что код сказать не может, — почему так сделано. Плохой пересказывает что делает код, дублируя его, и со временем начинает врать: код поправили, комментарий забыли.

// плохо: пересказ кода — устареет при первой правке
// увеличиваем счётчик на 1
counter++;

// хорошо: объясняет неочевидное «почему»
// Retry ровно 3 раза: у платёжного шлюза лимит 3 попытки на идемпотентный ключ.
retry(3, () -> gateway.charge(token));

Лучший комментарий — тот, который удалось не писать, заменив его выразительным именем. Если тянет пояснить кусок комментарием — сначала попробуйте вынести его в функцию с говорящим именем. Комментарии оправданы для «почему», для предупреждений о неочевидных последствиях и для публичного API. Закомментированный «на всякий случай» код — не комментарий, а мусор: для истории есть git.

Форматирование и структура

Читаемость — это и визуальный порядок. Связанные строки держат рядом, пустой строкой отделяют смысловые блоки, вложенность держат неглубокой. Глубокие if внутри if разворачивают ранним выходом:

// лесенка — читатель держит в голове все условия
if (user != null) {
    if (user.isActive()) {
        // ... основная логика в глубине
    }
}

// guard-и: невалидные случаи отсекли сверху, основная логика — слева
if (user == null) return;
if (!user.isActive()) return;
// ... основная логика, без вложенности

Единый стиль в проекте важнее личных предпочтений — за это отвечает автоформаттер, а не споры на ревью.

Дублирование и границы

DRY (Don't Repeat Yourself): одно знание живёт в одном месте. Три скопированных блока с расчётом цены — это три места, где завтра забудут поправить налог. Но и здесь без фанатизма: два внешне похожих куска, которые меняются по разным причинам, — не дубликат, и склеивать их вредно (получится связанность на ровном месте).

Работу с внешним миром — базой, чужим API, файлами — прячут за тонкой границей (репозиторий, клиент), чтобы детали не растекались по всему коду. Тогда замена библиотеки или базы затрагивает одно место, а не половину проекта.

Когда чистота превращается в догму

Clean Code — набор ориентиров, а не свод законов. Довести до абсурда можно любой:

  • Дробление на функции по две строки, когда логика читается лучше в одной, — уже не ясность, а прыжки по файлу.
  • «Ни одного комментария» — плохо ровно так же, как «комментарий на каждой строке»: неочевидное «почему» должно быть записано.
  • DRY, доведённый до склейки всего похожего, рождает универсальных монстров с десятью флагами.

Ориентир простой: чистый код — тот, при чтении которого следующий человек не задаёт лишних вопросов. Если правило помогает этому — применяйте; если ради правила код стало читать труднее — правило проиграло.

Коротко

  • Код читают чаще, чем пишут; чистота — это про стоимость будущих изменений, а не про эстетику.
  • Имена раскрывают намерение и не требуют расшифровки; единый словарь важнее краткости.
  • Функция делает одно на одном уровне абстракции; булев флаг-аргумент — обычно сигнал разделить её надвое.
  • Комментарий объясняет «почему», а не пересказывает «что»; лучший комментарий заменяется выразительным именем.
  • Ранние выходы вместо глубокой вложенности; единый формат — на автоформаттере.
  • DRY — одно знание в одном месте, но без склейки того, что меняется по разным причинам.
  • Всё это — ориентиры, а не догма: правило, из-за которого код читать труднее, проиграло.

Что почитать дальше

  • SOLID: пять принципов проектирования — как чистые функции складываются в чистую структуру классов.
  • GRASP: кто за что отвечает — принципы распределения ответственности между объектами.
  • Паттерны GoF — готовые решения типичных задач проектирования.