Если вы когда-либо сталкивались с чужим кодом (или даже со своим, написанным полгода назад), то знаете, как сложно бывает понять, что именно делает тот или иной фрагмент. В такие моменты особенно остро ощущается потребность в пояснениях. Но какие есть способы, помогающие сделать код понятным?

Разберемся вместе.

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

С тех пор многое изменилось: большинство задач теперь решается с использованием языков высокого уровня. И вместе с этим возникает закономерный вопрос — действительно ли нам всё еще нужны комментарии? Или современный код может (и должен) объяснять себя сам?


Почему комментарии — не всегда добро

Комментарии изначально задумывались как способ сделать код понятнее. Но на практике они могут обернуться ловушкой.

Во-первых, они устаревают. Код меняется, бизнес-логика эволюционирует, а комментарии… забываются. И вот уже в тексте говорится одно, а код делает совершенно другое.

Иногда встречается еще одна форма «устаревания» комментариев — когда они теряют связь с тем кодом, который должны пояснять. Даже если сам комментарий остается актуальным по смыслу, проблема в том, что между ним и соответствующим фрагментом кода со временем могли появиться дополнительные строки. В итоге понять, к чему относится этот комментарий, становится настоящим испытанием: приходится прокручивать в уме несколько возможных связей, чтобы уловить его первоначальный смысл.

Во-вторых, они часто маскируют проблему. Вместо того чтобы улучшить читаемость, разработчик просто дописывает комментарий к непонятной строке. Это сигнал: коду не хватает выразительности.


Что такое самодокументируемый код

Самодокументируемый код — это стиль написания, при котором смысл заложен в самом коде, а не в сопровождающем тексте.

В чём его сила:

  • Говорящие имена. Переменные, функции, классы называют так, чтобы их назначение было очевидно без дополнительных пояснений.

  • Мелкие, логичные методы. Сложность разбивается на части, каждая из которых выполняет одну понятную задачу.

  • Минимум сюрпризов. Читая такой код, вы чувствуете себя уверенно. Он ведет за собой, как хорошо написанная инструкция.


Когда код говорит сам за себя: несколько примеров

? Было:

// Вычисляем общую стоимость заказа с учетом налогов и скидок
public double calculate(double price, double tax, double discount) {
    return price + (price * tax) - discount;
}

? Стало:

public double calculateTotalOrderPrice(double price, double taxRate, double discountAmount) {
    return price + (price * taxRate) - discountAmount;
}

Комментарий исчез — но смысла стало даже больше. Имя метода и параметры делают всё понятным с первого взгляда.


Еще пример:

? Было:

# Проверяем, является ли число чётным
if number % 2 == 0:
    print("Четное")

? Стало:

def is_even(number):
    return number % 2 == 0

if is_even(number):
    print("Четное")

Появилась функция с хорошим именем — и вот уже комментарий не нужен. Понимание приходит сразу.


А как быть со сложной логикой?

Самодокументируемый код особенно хорош, когда дело доходит до предикатов.

? Было:

// Проверяем, если пользователь не заблокирован и подписка активна
if (!user.isBlocked && user.subscription.isActive) {
    sendNewsletter();
}

? Стало:

if (user.canReceiveNewsletter()) {
    sendNewsletter();
}

Метод canReceiveNewsletter() говорит сам за себя. Условие скрыто — но смысл раскрыт. Такой подход не только улучшает читаемость, но и упрощает повторное использование логики.


Что говорит об этом «Чистый код»

Роберт Мартин в своей книге «Чистый код» настойчиво подчеркивает: лучший комментарий — это тот, который не нужен. Если код требует пояснений — это сигнал, что его можно улучшить.

Он призывает:

  • Извлекать методы с понятными названиями.

  • Давать осмысленные имена переменным.

  • Избегать лишних комментариев.

  • Делать код максимально прозрачным и понятным.


Но не все так однозначно

Это не значит, что комментарии должны исчезнуть навсегда. Иногда они необходимы:

  • Чтобы объяснить почему сделано именно так, а не иначе.

  • Чтобы задокументировать сторонние ограничения.

  • Чтобы указать на временные решения или технический долг.

Но они должны быть исключением, а не правилом.


Заключение

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

Следующий раз, когда потянетесь к //, спросите себя: а можно ли сделать код настолько понятным, чтобы он сам за себя говорил?

Скорее всего — можно.

Комментарии (9)


  1. delphinpro
    21.07.2025 13:10

    лучший комментарий — это тот, который не нужен

    Формулировка неоднозначная. Если комментарий не нужен, но он есть, то это не лучший комментарий.


  1. SpiderEkb
    21.07.2025 13:10

    Комментировать что делает одна строка кода малоосмысленно.

    А вот когда у вас есть блок кода, который реализует какую-то логику, причем, с максимальной эффективностью (т.е. далеко не всегда "в лоб"), то тут потребуются комментарии - что делает это блок, почему он делает это именно так, а не иначе.

    Или когда есть достаточно объемное ТЗ со сложной логикой. И вам нужно привязать определенные блоки кода к определенным пунктам ТЗ. Тут без комментариев не обойтись.

    И да. Комментарии нужно актуализировать вместе с кодом. Ну и осмысленные имена переменных и функций тоже никто не отменял.


    1. iv660 Автор
      21.07.2025 13:10

      Всё это так. Смысл в том, что во всех этих случаях комментариям есть достойные альтернативы. По опыту, почти в 100 % случаев, когда есть желание написать комментарий, можно извлечь соотвествующий фрагмент кода в метод, и имя этого метода будет заменой комментарию.

      И как правило, это будет лучшей альтернативой, потому что имя метода (более-менее) неразрывно связано с его назначением с одной стороны и с реализацией — с другой. О комментарии такого не скажешь.


      1. SpiderEkb
        21.07.2025 13:10

        когда есть желание написать комментарий, можно извлечь соответствующий фрагмент кода в метод, и имя этого метода будет заменой комментарию

        Не всегда. Когда пишется что-то реально объемное и реально сложное, каждые 2-3 строки в метод выносить - только запутывать код. Не говоря уже о том, что это снижение производительности (каждый вызов метода - создание, а потом свертывание еще одного уровня стека). Если все это вас не волнует - вам повезло.

        Метод - это некая законченная логическая единица. И она может быть достаточно сложной логически и неочевидной. Метод "каким клиентам нужно посылать уведомление" описывается в ТЗ на 10-15-ти страницах, а в коде выливается в 5 выборок (каждая из которых есть SQL почти на станицу по нескольким таблицам с кучей условий), а потом еще каждый элемент выборки проверяется по 3-4 дополнительным условиям (если еще и их в запрос включать, он будет непозволительно долго выполнятся т.к. станет безумно сложным).

        И всегда останется вопрос - почему в методе needClientSendNotification эта самая необходимость проверяется именно по признакам А, Б и В именно в этой таблице, на не признакам Г, Д и Е в другой таблице (потому что можно и так и этак).

        Вот простой пример. Нужно

        Найти в таблице HDAPF наличие записи по условию
        • HDACUS = CUS
        • HDACLC = CLC
        • HDATYP = ‘DOC’
        • HDAMBN = 1,4,5
        • И максимальным HDACRD

               SetGT ($Cus: $Clc: 'DOC') HDA02LF;
               readp HDA02LF;
        
               dow not %eof(HDA02LF) and 
                   HDACUS = $Cus and 
                   HDACLC = $Clc and 
                   HDATYP = 'DOC';
                 if HDAMBN in %list('1': '4': '5');
                   @DAT = HDADAT;
                   leave;
                 endif;
        
                 readp HDA02LF;
               enddo;

        Чтобы понять что этот код работает правильно (а первая реакция человека будет - "а где проверка на максимальный CRD - его тут вообще нигде нет"), вам потребуется заглянуть в структуру индекса HDA02LF

        И только там вы увидите что

             A                                      UNIQUE
             A          R HDAPFR                    PFILE(HDAPF)
             A          K HDACUS
             A          K HDACLC
             A          K HDATYP
             A          K HDACRD

        Последнее поле - HDACRD. Т.е. все записи группы HDACUS-HDACLC-HDATYP отсортированы по возрастанию CRD. И поэтому поставив указатель на "максимальное значение в группе" (SetGT) и прочитав "запись назад" мы получим именно запись с максимальным CRD. Ну а дальше проверяем допусловие по HDAMBN и если онj не выполнятся - читаем еще назад пока не найдем нужное (а как нашли - выходим из цикла).

        И как вы не выносите это в отдельный метод и как не обзывайте - понятнее не станет, если не знать структур индекса. А чтобы ее узнать, вам нужно отвлечься от кода и куда-то там дополнительно лезть - смотреть что за индекс, по каим полям, есть ли там дополнительные фильтры (например, по признаку активна запись или нет...) и т.п.

        Но достаточно добавить комментарий

               // HDA02LF отсортирован по HDACRD. 
               // Посему ставим на конец цепочки CUS-CLC-DOC и идет назад пока не найдем запись с HDAMBN = 1,4,5

        Как все становится очевидно.


        1. simplepersonru
          21.07.2025 13:10

           Не говоря уже о том, что это снижение производительности (каждый вызов метода - создание, а потом свертывание еще одного уровня стека). Если все это вас не волнует - вам повезло.

          Инлайнинг, оптимизация хвостовой рекурсии?


  1. vk6677
    21.07.2025 13:10

    Хорошо, когда комментарий описывает более подробно использование методов. Например, в расчёте стоимости с учётом налога (что приведён в статье) только по коду можно понять что налог не в процентах, а в долях. Если не видно реализации метода это не очевидно.

    Тогда стоит более точно указывать название аргументов: например, taxRateNotPercent. По мне, лучше словами описать.


    1. iv660 Автор
      21.07.2025 13:10

      Да, оба варианта возможны, тут нужно смотреть по контексту.

      От себя дополню, что предложенная вами формулировка NotPercent может оказаться не вполне однозначной. Я бы предлжил fractionalTaxRate.

      Плюс самодокументируемого кода в вашем кейсе становится понятен, если тело метода с расчетом оказывается относительно большим. Тогда расстояние от места объявления аргумента или локальной переменной (там, где она описана комментарием) до места его использования может оказаться достаточно большим, чтобы вызвать непонимание у человека, читающего код. В случае самодокументируемого кода имя переменной говорит само за себя, в каком бы месте оно вам ни встретилось.


  1. Abstraction
    21.07.2025 13:10

    Комментарии могут размечать логические блоки в пределах функции (а вынести их в отдельные именованные функции может быть плохой идеей из-за большого количества совместно используемых переменных). Более того, один из способов дизайна методов - вначале записать комментариями предполагаемую логику работы, а затем реализующий её код (приём взят из "Совершенного кода" Макконнелла).

    Комментарии к публичным интерфейсам полезны, потому что передают семантику использования и неочевидные ограничения, которые затруднительно заложить в имена:

    class GeoLine {
    public:
      //! \brief возвращает длину линии в _метрах_, потенциально затратный вызов
      //! \warning в случае местной системы координат длина будет в единицах проекции
      double Length(void) const;
      //! \brief возвращает длину линии в _единицах проекции_
      double LengthUnits(void) const;
    }

    Или содержат примеры использования:

    // \example LOG_TIME(Info) foo(); //выведет в лог время выполнения foo()
    #define LOG_TIME(Lv) if(log::impl::timer<Lv> t; t)

    Комментарии могут содержать напоминания - знание, которое надо не забыть учесть при изменении кода (даже если прямо сейчас это знание не порождает какого-то решения, про которое надо объяснять "почему"):

    int foo(double* xBegin, double* xEnd, const double* yBegin){
      // yBegin имеет право совпадать с xBegin
      //...
    }

    Наконец, исключение "чтобы объяснить почему сделано именно так, а не иначе" - очень резиновое. Ведь такие объяснения возможны в любой сколько-то нетривиальной функции:

    /// Какой из этих трёх комментариев здесь уместнее? Или никакой?
    // определяем, с какой стороны луча находится точка
    // подставляем точку в уравнение прямой
    // наша прямая aX+bY+c=0, подстановка (p.x,p.y) даёт знаковое расстояние
    double dist = a*p.x + b*p.y + c;
    if(dist > 0) { //справа по направлению обхода
      //...
    } else {
      //...
    }


    1. SpiderEkb
      21.07.2025 13:10

      вынести их в отдельные именованные функции может быть плохой идеей из-за большого количества совместно используемых переменных

      Да, тоже хотел упомянуть, но не получилось так точно сформулировать.

      Комментарии к публичным интерфейсам полезны, потому что передают семантику использования и неочевидные ограничения, которые затруднительно заложить в имена

      Затруднительно - это еще мягко сказано. Писать имена переменных "параметр_который_не_может_быть_больше_5_и_меньше_1" - ну такое себе...

      Наконец, исключение "чтобы объяснить почему сделано именно так, а не иначе" - очень резиновое. Ведь такие объяснения возможны в любой сколько-то нетривиальной функции

      Именно. И эти объяснения опять таки в имя никак не внести. В целом это может быть краткое описание алгоритма которое сильно помогает понимать что именно делает код.

      Как-то пришлось делать модуль для транслитерации. Особенность была в том, что "ЯКОВ" транслитерируется в "YAKOV", а "Яков" в "Yakov" Или "Я." в "Ya." Т.е. регистр второго знака в сочетании зависел от контекста.

      И проще эти правила написать в комментарии чем потом по коду все это дело расковыривать. Если человек, который потом будет с этим кодом работать, прочтет комментарий и сразу будет понимать что оно должно делать, ему будет легче воспринимать код.