Если вы когда-либо сталкивались с чужим кодом (или даже со своим, написанным полгода назад), то знаете, как сложно бывает понять, что именно делает тот или иной фрагмент. В такие моменты особенно остро ощущается потребность в пояснениях. Но какие есть способы, помогающие сделать код понятным?
Разберемся вместе.
Когда автор этих строк делал первые шаги в программировании, одним из основных языков был ассемблер. В те времена подробные комментарии к каждой строке считались необходимостью — не столько по привычке, сколько по объективной сложности самого языка. Понять ассемблерный код без пояснений было практически невозможно.
С тех пор многое изменилось: большинство задач теперь решается с использованием языков высокого уровня. И вместе с этим возникает закономерный вопрос — действительно ли нам всё еще нужны комментарии? Или современный код может (и должен) объяснять себя сам?
Почему комментарии — не всегда добро
Комментарии изначально задумывались как способ сделать код понятнее. Но на практике они могут обернуться ловушкой.
Во-первых, они устаревают. Код меняется, бизнес-логика эволюционирует, а комментарии… забываются. И вот уже в тексте говорится одно, а код делает совершенно другое.
Иногда встречается еще одна форма «устаревания» комментариев — когда они теряют связь с тем кодом, который должны пояснять. Даже если сам комментарий остается актуальным по смыслу, проблема в том, что между ним и соответствующим фрагментом кода со временем могли появиться дополнительные строки. В итоге понять, к чему относится этот комментарий, становится настоящим испытанием: приходится прокручивать в уме несколько возможных связей, чтобы уловить его первоначальный смысл.
Во-вторых, они часто маскируют проблему. Вместо того чтобы улучшить читаемость, разработчик просто дописывает комментарий к непонятной строке. Это сигнал: коду не хватает выразительности.
Что такое самодокументируемый код
Самодокументируемый код — это стиль написания, при котором смысл заложен в самом коде, а не в сопровождающем тексте.
В чём его сила:
Говорящие имена. Переменные, функции, классы называют так, чтобы их назначение было очевидно без дополнительных пояснений.
Мелкие, логичные методы. Сложность разбивается на части, каждая из которых выполняет одну понятную задачу.
Минимум сюрпризов. Читая такой код, вы чувствуете себя уверенно. Он ведет за собой, как хорошо написанная инструкция.
Когда код говорит сам за себя: несколько примеров
? Было:
// Вычисляем общую стоимость заказа с учетом налогов и скидок
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)
SpiderEkb
21.07.2025 13:10Комментировать что делает одна строка кода малоосмысленно.
А вот когда у вас есть блок кода, который реализует какую-то логику, причем, с максимальной эффективностью (т.е. далеко не всегда "в лоб"), то тут потребуются комментарии - что делает это блок, почему он делает это именно так, а не иначе.
Или когда есть достаточно объемное ТЗ со сложной логикой. И вам нужно привязать определенные блоки кода к определенным пунктам ТЗ. Тут без комментариев не обойтись.
И да. Комментарии нужно актуализировать вместе с кодом. Ну и осмысленные имена переменных и функций тоже никто не отменял.
iv660 Автор
21.07.2025 13:10Всё это так. Смысл в том, что во всех этих случаях комментариям есть достойные альтернативы. По опыту, почти в 100 % случаев, когда есть желание написать комментарий, можно извлечь соотвествующий фрагмент кода в метод, и имя этого метода будет заменой комментарию.
И как правило, это будет лучшей альтернативой, потому что имя метода (более-менее) неразрывно связано с его назначением с одной стороны и с реализацией — с другой. О комментарии такого не скажешь.
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
Как все становится очевидно.
simplepersonru
21.07.2025 13:10Не говоря уже о том, что это снижение производительности (каждый вызов метода - создание, а потом свертывание еще одного уровня стека). Если все это вас не волнует - вам повезло.
Инлайнинг, оптимизация хвостовой рекурсии?
vk6677
21.07.2025 13:10Хорошо, когда комментарий описывает более подробно использование методов. Например, в расчёте стоимости с учётом налога (что приведён в статье) только по коду можно понять что налог не в процентах, а в долях. Если не видно реализации метода это не очевидно.
Тогда стоит более точно указывать название аргументов: например, taxRateNotPercent. По мне, лучше словами описать.
iv660 Автор
21.07.2025 13:10Да, оба варианта возможны, тут нужно смотреть по контексту.
От себя дополню, что предложенная вами формулировка NotPercent может оказаться не вполне однозначной. Я бы предлжил fractionalTaxRate.
Плюс самодокументируемого кода в вашем кейсе становится понятен, если тело метода с расчетом оказывается относительно большим. Тогда расстояние от места объявления аргумента или локальной переменной (там, где она описана комментарием) до места его использования может оказаться достаточно большим, чтобы вызвать непонимание у человека, читающего код. В случае самодокументируемого кода имя переменной говорит само за себя, в каком бы месте оно вам ни встретилось.
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 { //... }
SpiderEkb
21.07.2025 13:10вынести их в отдельные именованные функции может быть плохой идеей из-за большого количества совместно используемых переменных
Да, тоже хотел упомянуть, но не получилось так точно сформулировать.
Комментарии к публичным интерфейсам полезны, потому что передают семантику использования и неочевидные ограничения, которые затруднительно заложить в имена
Затруднительно - это еще мягко сказано. Писать имена переменных "параметр_который_не_может_быть_больше_5_и_меньше_1" - ну такое себе...
Наконец, исключение "чтобы объяснить почему сделано именно так, а не иначе" - очень резиновое. Ведь такие объяснения возможны в любой сколько-то нетривиальной функции
Именно. И эти объяснения опять таки в имя никак не внести. В целом это может быть краткое описание алгоритма которое сильно помогает понимать что именно делает код.
Как-то пришлось делать модуль для транслитерации. Особенность была в том, что "ЯКОВ" транслитерируется в "YAKOV", а "Яков" в "Yakov" Или "Я." в "Ya." Т.е. регистр второго знака в сочетании зависел от контекста.
И проще эти правила написать в комментарии чем потом по коду все это дело расковыривать. Если человек, который потом будет с этим кодом работать, прочтет комментарий и сразу будет понимать что оно должно делать, ему будет легче воспринимать код.
delphinpro
Формулировка неоднозначная. Если комментарий не нужен, но он есть, то это не лучший комментарий.