Главная Веб-разработка Комментарии в коде: когда помогают, а когда мешают — разбор с примерами

Комментарии в коде: когда помогают, а когда мешают — разбор с примерами

от admin

Когда стоит писать комментарии в коде, а когда они только мешают. Разбираем виды комментариев, их пользу и вред на реальных примерах.

082 открытий326 показов

Что делать с комментариями в коде — писать или не писать? Одни уверены: чистый код говорит сам за себя, другие не представляют работу без пояснений. Истина, как обычно, посередине. Комментарии — это инструмент, умелый программист применяет их с пользой, неумелый — только усложняет жизнь себе, коллегам, начальству, пользователям и вообщем всем сопричастным. Разберемся, когда комментарии действительно нужны, а когда от них больше вреда и приведем примеры в коде

Зачем вообще писать комментарии в коде?

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

Какие задачи они решают ?

1. Пояснить неочевидное

Код показывает «что» делает программа, а комментарий — «зачем».

			def find_user(users, target_id):     # Используем бинарный поиск, потому что список отсортирован по ID     left, right = 0, len(users) - 1     while left <= right:         mid = (left + right) // 2         if users[mid]['id'] == target_id:             return users[mid]         elif users[mid]['id'] < target_id:             left = mid + 1         else:             right = mid - 1     return Nonedef find_user(users, target_id):     # Используем бинарный поиск, потому что список отсортирован по ID     left, right = 0, len(users) - 1     while left <= right:         mid = (left + right) // 2         if users[mid]['id'] == target_id:             return users[mid]         elif users[mid]['id'] < target_id:             left = mid + 1         else:             right = mid - 1     return None 		

Такой комментарий не просто поясняет логику — он экономит десятки минут будущего чтения.

2. Предупредить

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

			function scrollToTop() {   // Не убирай эту задержку -- иначе не работает на iOS Safari   setTimeout(() => {     window.scrollTo(0, 0);   }, 100); } 		

3. Пометить незавершёнку

TODO, FIXME, HACK — это специальные маячки. Их ставят туда, где нужно что-то доделать, починить или переписать по-человечески.

			// TODO: заменить на асинхронный вызов после перехода на новую версию API public void syncData() {     downloadData();     saveToDatabase(); } 		

Бонусом, IDE умеют обрабатывать такие заметки, каждая по-своему.

4. Временно отключить код

Иногда нужно что-то закомментировать, чтобы проверить гипотезу. Но такой код нельзя оставлять надолго. Если от него нет пользы — в мусор. Историю всё равно сохранит git.

			// old behavior -- временно отключено для тестирования // int result = legacyCalculate(x, y); int result = newCalculate(x, y); 		

5. Объяснить архитектуру

Иногда важно не только «как» сделано, но и «почему так». Особенно это касается паттернов, нестандартных решений или компромиссов.

			// Используем пул воркеров вместо карутин на каждый запрос, // чтобы ограничить нагрузку на базу startWorkerPool(db, 10) 		

6. Генерация документации

Многие языки и фреймворки поддерживают специальные форматированные комментарии для генерации документации. Например, JavaDoc в Java, docstring в Python, XML в C# – предназначены для описания интерфейсов: что делает функция или класс, какие имеют входные параметры и какой результат дают. Такие Комментарии выполняют роль пользовательской документации прямо в коде и могут автоматически собираться в справочник по API.

			def get_temperature(city):     """     Получает текущую температуру в указанном городе.          Args:         city (str): Название города          Returns:         float: Температура в градусах Цельсия     """     ... 		

7. Пошутить

Программисты – тоже люди, и иногда оставляют в коде шуточные либо эмоциональные комментарии, чтобы снять стресс. В открытых исходниках можно встретить комментарии с шутками, сарказмом или даже ругательствами, адресованными сложному коду или «костылям».

			// Если вы это читаете -- у вас слишком много свободного времени void debugSomethingWeird() {     ... } 		

Как видно, диапазон применения комментариев очень широк. Но одинаково ли хорошо все эти виды влияют на качество кода? Рассмотрим случаи, когда комментарии приносят пользу, а когда создают проблемы.

Когда комментарии помогают

А часто без комментариев в коде сложно разобраться, особенно когда код чужой.

1. Когда логика не лежит на поверхности

Есть участки кода, где без контекста трудно разобраться, даже если код написан довольно читабельно. Например, сложная формула, нетривиальный алгоритм или необычная структура данных – всё, что выбивается из обыденного опыта разработчиков. В таких случаях пара строк комментария, резюмирующих подход, или объясняющих, что происходит, сэкономят часы на анализ. Это особенно важно для командной работы: коллегам, незнакомым с модулем, не придётся разбираться «с нуля».

			# Вычисляем отклонение по нестандартной метрике, потому что так хочет клиент. # Формула придумана в отделе аналитики, она не является общепринятой. def calculate_score(x, y, z):     numerator = x ** 2 + y ** 2 - x * y     score = numerator / z     return score 		

2. Когда нужно документировать контракты и условия

Комментарии могут использоваться для обозначения контрактов – предусловий и постусловий функций, инвариантов и т.д. (подход Design by Contract). Хотя современные языки позволяют выразить многое (например, через assert или декораторы), комментарии могут дополнять код уточнениями вроде:

			/**  * Предусловие: массив не должен быть пустым.  * Постусловие: функция возвращает минимальное значение в массиве.  */ function findMin(arr) {   if (arr.length === 0) {     throw new Error("Массив не должен быть пустым");   }   let min = arr[0];   for (let i = 1; i < arr.length; i++) {     if (arr[i] < min) {       min = arr[i];     }   }   return min; } 		

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

			/**  * Если массив содержит нечисловые значения -- поведение не определено.  */ function findMin(arr) {   if (arr.length === 0) {     throw new Error("Массив не должен быть пустым");   }   let min = arr[0];   for (let i = 1; i < arr.length; i++) {     if (arr[i] < min) {       min = arr[i];     }   }   return min; } 		

Да, можно это выразить через assert, но комментарий дает сразу и контекст, и предупреждение. Особенно если логика непростая.

3. Когда нужно предоставить контекст и ссылки

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

			// Реализация по статье Джонсона: "Fast Matrix Calc", 2020 // https://example.com/fast-matrix  public double[][] fastMultiply(double[][] a, double[][] b) {     int n = a.length;     double[][] result = new double[n][n];     for (int i = 0; i < n; i++) {         for (int j = 0; j < n; j++) {             for (int k = 0; k < n; k++) {                 result[i][j] += a[i][k] * b[k][j];             }         }     }     return result; } 		

4. Когда нужно облегчить ревью

Когда код содержит много пояснений к его работе новому участнику команды проще входить в проект – по сути, комментарии выполняют роль встроенной документации. Кроме того, код-ревью проходит эффективнее, если автор сразу помечает неочевидные места комментариями. Например:

			# Компенсируем округление при делении -- иначе может теряться один пиксель на границе x = int(width / scale) + 1 y = int(height / scale) + 1 canvas.resize(x, y) 		

В PEP 8 (стиле кодирования Python) прямо приводится пример: комментарий “Compensate for border” – полезный, в отличие от банального “Increment x”, который не даёт новой информации.

5. Когда нужна поддержка самодокументируемости через структуру

Иногда хочется написать: # Этап 1: авторизация пользователя, и в этот момент приходит мысль — а почему бы не вынести это в функцию authorize_user()? И комментарий уже не нужен. То есть сам порыв объяснить словами часто указывает, что код пора расчленить и упростить.

			// Этап 1: проверка токена и загрузка сессии $userId = checkToken($_COOKIE['auth_token']); $session = loadSession($userId);  // Этап 2: подготовка данных профиля $profile = getUserProfile($userId); $settings = getUserSettings($userId);  // Этап 3: отрисовка страницы renderProfilePage($profile, $settings); 		

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

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

			// Используем делегирование событий для оптимизации. // Не вешаем обработчик на каждую кнопку отдельно. document.querySelector('#list').addEventListener('click', function(event) {   if (event.target.matches('.remove-button')) {     removeItem(event.target.dataset.id);   } }); 		

Комментарий — это инструмент. Если комментарий в коде дает информацию, которую нельзя вытащить из кода напрямую, — значит, работает как надо. Но бывает и обратное — когда комментарии мешают. Об этом — в следующей части.

Когда комментарии вредят

Худшее, что может случиться с комментариями – когда они вводят в заблуждение или засоряют код впустую. Рассмотрим подробнее:

Читать также:
Синий экран смерти: почему он появляется и как его победить

Дублируют очевидное

Комментарий, который просто повторяет код своими словами, не несёт никакой пользы:

			int i = 0; while (i < 10) {     i = i + 1;  // увеличиваем i на 1 (плохой комментарий)     std::cout << i << std::endl; } 		

А вот так — лучше вообще без пояснений:

			for (int i = 1; i <= 10; ++i) {     std::cout << i << std::endl; } 		

Комментарий должен объяснять, зачем что-то делается, а не что именно:

			// Начинаем с 1, потому что 0-й элемент -- это контрольная запись for (int i = 1; i <= data.size(); ++i) {     process(data[i]); } 		

Врут и вводят в заблуждение

Классика: код переписали, а про комментарий забыли.

			# TODO: вызываем старый API до миграции на новый response = new_api_call(data) if response.ok:     handle_response(response) 		

Спустя некоторое время код могли переписать, и old_api_call() заменили на new_api_call(), но комментарий остался от прежней версии и стал источником дезинформации: разработчик, читающий код, может принять заведомо неверное решение, доверившись устаревшей заметке. По этой причине крайне важно понимать: если уж пишете комментарий, держите его в актуальном состоянии вместе с кодом.

Показывают, что код плохой

Когда код плохо читается, и его пытаются «объяснить» словами, вместо того чтобы переписать:

			// Проверяем наличие ключа, затем извлекаем значение, если оно определено if (data && data.hasOwnProperty('result') && data.result !== undefined) {     const value = data.result;     if (typeof value === 'string') {         console.log("Результат:", value.trim());     } } 		

Вместо этого — понятный код:

			const value = data?.result; if (typeof value === 'string') {     console.log("Результат:", value.trim()); } 		

Плодятся бесконтрольно

Иногда встречается код, где каждое действие сопровождается избыточными пояснениями:

			// Создаем список имен List names = new ArrayList<>();  // Добавляем имя в список names.add("Alice");  // Добавляем ещё одно имя names.add("Bob");  // Удаляем имя names.remove("Alice");  // Проверить, пустой ли список if (names.isEmpty()) {     // Если пустой, выводим сообщение     System.out.println("Список пуст"); } 		

Здесь нет ни одной неочевидной строки. Лучше оставить так:

			List names = new ArrayList<>(List.of("Alice", "Bob")); names.remove("Alice");  if (names.isEmpty()) {     System.out.println("Список пуст"); } 		

Представляют собой мёртвый код

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

			// int result = legacy_compute(data); // if (result > 0) { //     printf("Успешно: %dn", result); // } else { //     printf("Ошибка: %dn", result); // } 		

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

Запутывают и размывают смысл

			// временное решение handleLegacyRequest(input); 		

Что значит «временное»? Когда переделывать? Почему не постоянное?

Лучше так:

			// Используем старую версию запроса до выхода обновлённого backend-интерфейса (см. тикет #231) handleLegacyRequest(input); 		

Теперь ясно, почему так сделано, и можно отследить, когда это поведение закончится.

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

Самодокументируемый код vs комментарии

Есть мечта у программистов — писать код, который объясняет сам себя. Без сносок, без подсказок, без комментариев. Такой код читается как инструкция: открыл — понял. Это и называется самодокументируемым стилем.

Как его достигают?

  • Говорящие имена. Не x и a, а temperature, retryLimit, userProfile. Лучше сразу по имени понять, что перед тобой — массив с ID или словарь с настройками.
  • Функции с характером. У функции должно быть имя-глагол, в котором есть ответ на вопрос “что делает этот код?”. Например: parseInvoice(), sendEmailReminder(), fetchUserByToken().
  • Использовать названия для константных значений. Вместо if (status == 4) — if (status == ORDER_CONFIRMED). Вместо 3000 — RETRY_TIMEOUT_MS.
  • Структура — как абзацы в тексте. Пустые строки, логические блоки, отступы — чтобы этапы выделялись сами собой.

Посмотрим например, где простой код поясняется ненужным комментарием:

			# Подсчёт средней температуры за период s = 0 for x in data:     s += x avg = s / len(data) print("Average temperature:", avg) 		

Другой пример:

			temperatures = [22.4, 23.1, 21.8, 24.0, 22.9]  total_temperature = sum(temperatures) average_temperature = total_temperature / len(temperatures)  print(f"Средняя температура за период: {average_temperature:.1f}°C") 		

Комментарий уже не нужен. Из имён всё понятно: считаем среднюю температуру. Здесь самое интересное — само документируемость имеет границы. Потому что:

  • Код объясняет «что», но не всегда «почему».

Вот есть строка:

			retry_limit = 5 		

А почему 5? Почему не 3, не 10? Если причина — ограничение API, бизнес-логика или чья-то странная прихоть — код не расскажет. А вот комментарий может:

			# Максимум 5 попыток -- лимит API retry_limit = 5  for attempt in range(retry_limit):     success = send_request()     if success:         break     time.sleep(2) 		

Неочевидные решения и компромиссы

Иногда приходится делать что-то нестандартное. Например:

			// Отключаем сортировку -- иначе баг в Safari (см. issue #1832) const table = document.querySelector("#report-table"); table.sortable = false; // Альтернатива временно недоступна, до фикса багов в библиотеке 		

Такой кусок кода без пояснений вызовет недоумение: А почему не сортируем?

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

Влияние ИИ и новых инструментов на подход к комментариям

В последние годы в распоряжении разработчиков появились мощные ассистенты на базе искусственного интеллекта — такие как GitHub Copilot (автодополнение кода на основе ИИ) и большие языковые модели вроде ChatGPT. Эти технологии начинают влиять и на практики комментирования кода.

Во-первых, автогенерация кода по комментариям стала реальностью.

Инструмент Copilot способен на лету написать фрагмент кода, ориентируясь на описания на естественном языке. Например:

			// Функция проверяет, является ли число простым function isPrime(n) {     // (Copilot сам предложит код) } 		

Copilot тут же может подставить реализацию:

			function isPrime(n) {     if (n <= 1) return false;     if (n <= 3) return true;     if (n % 2 === 0 || n % 3 === 0) return false;     for (let i = 5; i * i <= n; i += 6) {         if (n % i === 0 || n % (i + 2) === 0) return false;     }     return true; } 		

Такой подход превращает комментарий в своего рода промпт — описание задачи для ИИ. Это приучает писать коротко и по делу. Хотя такие комментарии потом часто удаляют, их роль в генерации кода становится всё важнее.

Во-вторых, ИИ сам пишет комментарии.

Можно просто показать код:

			def convert_to_celsius(fahrenheit):     return (fahrenheit - 32) * 5 / 9 		

…и попросить ИИ: прокомментируй. В ответ он выдаст:

			def convert_to_celsius(fahrenheit):     # Конвертация температуры из Фаренгейта в Цельсий по стандартной формуле     return (fahrenheit - 32) * 5 / 9 		

Или даже более детальный:

			def convert_to_celsius(fahrenheit):     # Функция принимает температуру в градусах Фаренгейта     # и возвращает эквивалент в градусах Цельсия     return (fahrenheit - 32) * 5 / 9 		

Такие автокомментарии могут быть полезны, но всё равно требуют проверки: всезнающий ИИ не всегда понимает скрытые нюансы логики, а часто просто галлюцинирует, поэтому доверять полностью не стоит, но, как черновик, вполне годится.

В-третьих, стало проще понимать чужой код без комментариев.

Раньше приходилось часами вчитываться, сегодня — можно просто спросить:

			def process(data):     return [x * 2 for x in data if x % 3 == 0] 		

Запрос в ChatGPT: что делает эта функция?

Ответ: Она возвращает новый список, содержащий удвоенные значения тех элементов исходного списка, которые делятся на 3.

ИИ не волшебник, но в 90% случаев — удобный переводчик с машинного на человеческий. Это снимает часть нагрузки с необходимости документировать тривиальные вещи.

В-четвертых, новый тип комментариев: инструкции для ИИ

Как применять happens-before на практике и в чем основные преимущества этой концепцииtproger.ru

Может возникнуть ситуация, когда комментарий пишется не столько для человека, сколько как команда:

			# Оптимизировать этот метод, если входной список больше 1000 элементов def analyze_data(data):     ... 		

Если вы используете Cursor или Replit, это может очень полезным.

Заключение

Так всё-таки, комментарии – зло или спасение?

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

Но всё-таки чаще наличие комментариев делает жизнь лучше, ведь иногда формулирование мысли разговорным текстом, а не кодом, позволяет понять свою идею лучше, взглянуть на нее с другой стороны.

Похожие статьи