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

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

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

комментарий-заголовок — это имя функции, которая просится наружу void handleOrder(o) { // проверить заказ validate(o); // посчитать скидку total = applyDiscount(o); // записать в базу save(o, total); // отправить письмо notifyCustomer(o, total); } validate — только правила applyDiscount — только счёт save — только запись notifyCustomer — только письмо

Каждый кусок, который приходилось подписывать комментарием, уезжает в свою функцию с этим же именем. Сама handleOrder остаётся оглавлением из четырёх вызовов — её читают сверху вниз и не проваливаются в детали.

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

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

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

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

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

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

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

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

// так не надо: валидация, скидка, запись в БД и письмо — в одном теле
void handleOrder(Order o) { ... }

Та же обработка по шагам: каждый шаг проверяют и меняют отдельно.

живой пример

public class OrderFlow {
    record Order(String id, int amount) {}

    public static void main(String[] args) {
        handleOrder(new Order("ord-042", 1500));
    }

    static void handleOrder(Order o) {
        validate(o);
        int total = applyDiscount(o);
        save(o, total);
        notifyCustomer(o, total);
    }

    static void validate(Order o) {
        if (o.amount() <= 0) throw new IllegalArgumentException("сумма должна быть больше нуля");
    }

    static int applyDiscount(Order o) {
        return o.amount() >= 1000 ? o.amount() - o.amount() / 10 : o.amount();
    }

    static void save(Order o, int total) {
        System.out.println("сохранили " + o.id() + ", к оплате " + total);
    }

    static void notifyCustomer(Order o, int total) {
        System.out.println("письмо: заказ " + o.id() + " на сумму " + total);
    }
}
Запустить

Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Неделя бесплатно →

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

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

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

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

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

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

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

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

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

живой пример

public class Guards {
    record User(String name, boolean active) {}

    public static void main(String[] args) {
        User[] users = {new User("Анна", true), new User("Иван", false), null};
        for (User u : users) {
            System.out.println(nested(u) + "  |  " + guarded(u));
        }
    }

    static String nested(User u) {
        if (u != null) {
            if (u.active()) {
                return "пишем " + u.name();
            }
        }
        return "пропускаем";
    }

    static String guarded(User u) {
        if (u == null) return "пропускаем";
        if (!u.active()) return "пропускаем";
        return "пишем " + u.name();
    }
}
Запустить

Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Неделя бесплатно →

Обе отвечают одинаково — это видно в выводе. Разница в том, сколько условий читатель держит в голове, дойдя до главной строки.

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

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

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

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

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

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

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

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

Коротко

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

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