open atlas
↑ К треку
Паттерны и качество кода CP · 03 · 01

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

Комментарии не исполняются, поэтому гниют — а уверенно неверный комментарий хуже, чем никакого. Научись удалять плохие и оставлять те немногие, что заслужили место, объясняя ПОЧЕМУ код такой, а не ЧТО он делает.

CP Middle ◷ 18 min
Уровень
ОсновыJuniorMiddleSenior

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

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

Цель

После этого урока ты можешь назвать четыре семейства плохих комментариев (избыточные, устаревшие/вводящие в заблуждение, закомментированный код, баннерный шум) и удалять их с первого взгляда; сформулировать законные задачи, которые комментарий действительно решает — объяснить почему, а не что, предупредить о последствии, закодировать намерение, которое язык выразить не может, отметить TODO с тикетом; и распознать провал на другом полюсе: догму «никогда никаких комментариев», которая вырезает по-настоящему необходимое обоснование.

1

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

// increment i by one
i = i + 1;

// set the user's name to the given name
user.name = name;

// loop over all the items
for (const item of items) { /* ... */ }

Ни один из них не сообщает читателю ничего, чего код уже не кричит. Хуже: когда i = i + 1 позже становится i += 2, комментарий теперь неверен, и ты сфабриковал ложь из того, что было просто бесполезным. Лечение не в том, чтобы написать лучший пересказ — а в том, чтобы удалить комментарий и, если строка была неясна, сделать яснее код (имя получше, функцию поменьше). Код — источник истины; не перефразируй его.

2

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

// timeout is 30 seconds
const TIMEOUT_MS = 10_000;

// NOTE: assumes the list is already sorted
function median(xs: number[]): number {
  return xs[Math.floor(xs.length / 2)]; // returns the wrong element if xs is NOT sorted
}

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

3

Закомментированный код — мёртвый груз; удали его, система контроля версий помнит. Блок кода, «оставленный на всякий случай», обёрнутый в // или /* */, — один из самых частых и наименее оправданных комментариев.

function price(order: Order): number {
  // const tax = subtotal * 0.2;          // old flat rate
  // return subtotal + tax;
  // return subtotal + applyOldDiscount(subtotal);
  return subtotal + taxFor(subtotal, order.region);
}

Он не отвечает ни на один вопрос читателя. Это следующее, что отгрузят? Запасной путь? Заброшенный эксперимент? Никто не знает, поэтому все его оставляют — и он накапливается, гния бок о бок с живым кодом, путая каждый будущий поиск и ревью. Оригинал не потерян: git log -p и git blame хранят каждую строку, которую ты когда-либо удалял, вместе с коммитом, объясняющим почему. Удаление закомментированного кода ничего не теряет и убирает шум. Сохранение его ничего не сберегает и добавляет сомнение.

4

Хороший комментарий объясняет ПОЧЕМУ, а не ЧТО — единственное, что код по-настоящему сказать не может. Код превосходен в выражении того, что он делает и как. Чего он выразить не может — это рассуждение, которого нет в синтаксисе: ограничение, которое ты обходишь, последствие «безобидно» выглядящей правки, неочевидный выбор, сделанный намеренно.

// Stripe rounds half-up; we must match their cents exactly or the
// reconciliation job flags a 1-cent mismatch and pages on-call.
const cents = Math.round(amount * 100);

// DO NOT parallelise: the upstream API rejects concurrent writes
// to the same account with a 409 we can't retry cleanly.
for (const tx of txs) await post(tx);

// Binary search instead of indexOf: this runs per keystroke on a
// 50k-row list; linear scan dropped input latency below 60fps.
const i = lowerBound(rows, query);

Каждый из них заслуживает своего места. Ни один не повторяет код; каждый улавливает обоснование, которое будущему сопровождающему пришлось бы заново открывать, что-нибудь сломав. Лакмусовая бумажка: если бы ты удалил комментарий, смог бы грамотный читатель восстановить информацию из одного кода? Для что/как — да, значит комментарий это шум. Для почему (тот самый 409, сверка, бюджет в 60fps) — нет, значит комментарий делает реальную работу. Предупреждения о последствии, закодированное намерение, которое система типов уловить не может, и маркеры // TODO(JIRA-1234): …, привязанные к тикету, — всё это варианты одной законной задачи: записать то, что код выразить не может.

Разбор примера

Посмотри, как комментарий превращается в баг, а потом исправь правильную вещь. Валидатор отгружается с услужливо выглядящим комментарием:

// allow up to 3 failed login attempts before locking the account
function isLocked(attempts: number): boolean {
  return attempts > MAX_ATTEMPTS;
}

const MAX_ATTEMPTS = 5; // bumped from 3 after the support backlog last quarter

Комментарий говорит 3. Код блокирует после 5. Коллега, пишущий отчёт по безопасности, читает комментарий, заносит «аккаунты блокируются после 3 попыток», и аудит теперь неверен — не потому что неверен код, а потому что неверен комментарий. Комментарий был истинным когда-то; константа сдвинулась; ничто не потянуло прозу за собой.

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

function isLocked(attempts: number): boolean {
  return attempts > MAX_ATTEMPTS;
}

// 5, not 3: raised after Q2 support load showed legit users hitting
// the limit on shared office IPs. See SEC-204 before lowering.
const MAX_ATTEMPTS = 5;

Теперь что (порог равен 5) живёт только в коде, где не может дрейфовать, а почему (данные поддержки, тикет, предостережение против понижения) живёт в комментарии, где код по-настоящему сказать этого не мог. Избыточный пересказ ушёл; незаменимое обоснование осталось. В этом вся дисциплина.

Почему это работает

Почему почему-комментарий оправдан, когда мы только что четыре шага удаляли комментарии? Потому что нужный комментарий — это маленькое, честное признание: код не смог выразить намерение сам по себе. Тот самый 409 от внешнего API, задача сверки, поднимающая on-call, регуляция за магическим числом — ничего из этого не живёт ни в одном типе или имени. Комментарий тут — правильный инструмент, но он никогда не бесплатен: это всё ещё непроверенная проза, которую ты обязался сопровождать вручную. Поэтому порядок действий важен. Сначала попробуй сделать комментарий ненужным — имя получше, функцию поменьше, тип, кодирующий предусловие. Только когда код по-настоящему не может удержать смысл, тянись к комментарию, и тогда пиши ту часть, которую код сказать не может, а не ту, что он уже говорит.

Частая ошибка

Противоположный провал так же реален: догма «самодокументирующемуся коду комментарии не нужны», применённая абсолютно, которая удаляет и почему-комментарии тоже. Кто-то убирает // DO NOT parallelise: upstream rejects concurrent writes with a 409, видит, что цикл «очевидно» дожидается каждого вызова без причины, «прибирает» его в Promise.all — и заново вносит ровно тот баг, ради предотвращения которого комментарий и существовал. Самодокументирующийся код — цель для что и как — а комментарий никогда и не претендовал на документирование этого. Он документировал ограничение, живущее целиком вне кода. «Без комментариев» — хороший рефлекс против избыточных и протухших комментариев; как абсолютное правило оно вырезает то редкое, дорогое обоснование, сохранить которое было важнее всего. Удаляй комментарии, повторяющие код; защищай те, что его объясняют.

Проверь себя
Викторина

Ты находишь это на последовательном цикле записей в API: `// DO NOT parallelise: the upstream API returns a 409 on concurrent writes to one account`. Цикл выглядит так, будто его легко заменить на Promise.all. Что нужно сделать?

Итог

Комментарии не исполняются, поэтому ничто не держит их истинными, и они гниют — уверенно неверный комментарий хуже, чем никакого, потому что он стоит тебе времени на его опровержение. Удаляй четыре плохих семейства с первого взгляда: избыточные комментарии, повторяющие код, устаревшие/вводящие в заблуждение комментарии, отправляющие тебя по неверному пути, закомментированный код (система контроля версий его уже помнит) и баннерный шум. Оставляй только те комментарии, что делают единственную работу, неподвластную коду: объясняют ПОЧЕМУ, а не ЧТО — ограничение, последствие, неочевидное намерение, TODO, привязанный к тикету. Нужный комментарий — это честное признание, что код не смог выразить намерение: иногда неизбежен, никогда не бесплатен, всегда бремя сопровождения. И остерегайся противоположной крайности — догмы «никогда никаких комментариев», которая вырезает то редкое, незаменимое обоснование, сохранить которое было важнее всего.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 4 завершено

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.