Код читают чаще, чем пишут: одну строчку напишут раз, а перечитают её при каждой правке рядом — свои же и коллеги, через полгода и без контекста. Clean Code — набор привычек Роберта Мартина о том, как писать так, чтобы этот второй читатель понял код без археологических раскопок. Это не про красоту ради красоты, а про стоимость изменений: чем понятнее код, тем дешевле его менять.
Разберём главные привычки — не как догму, а как ответы на конкретную боль «почему я не понимаю, что тут происходит».
Имена, которые не надо расшифровывать
Имя — самый частый комментарий в коде. Хорошее имя объясняет смысл без пояснений:
// было — что такое d? в днях? в чём?
int d = (now - created) / 86400;
// стало — имя отвечает на вопрос само
int daysSinceRegistration = (now - created) / SECONDS_IN_DAY;
Несколько правил, которые сразу дают эффект:
- Имя раскрывает намерение.
list→activeUsers,flag→isEmailConfirmed,data→orderPayload. - Без загадочных сокращений.
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 — готовые решения типичных задач проектирования.