Когда комментарии врут
Комментарии не исполняются, поэтому гниют — а уверенно неверный комментарий хуже, чем никакого. Научись удалять плохие и оставлять те немногие, что заслужили место, объясняя ПОЧЕМУ код такой, а не ЧТО он делает.
Ты ловишь баг. Комментарий над функцией гласит // повторяет до 3 раз. Ты строишь всю свою ментальную модель на этой строке — пока через два часа наконец не читаешь сам цикл и не находишь, что он повторяет пять раз, потому что кто-то поднял константу год назад и так и не тронул комментарий. Комментарий тебе не помог. Он активно тебе соврал, а ты ему поверил, потому что комментарии выглядят авторитетно.
В этом и есть корневая проблема комментариев: компилятор их никогда не читает, тесты их никогда не проверяют, и ничто не заставляет их оставаться истинными. Код — единственное описание системы, которое обязано быть верным, потому что оно исполняется. Комментарий — это обещание, которое никто не принуждает выполнять, а непринуждаемое обещание дрейфует. Уверенно неверный комментарий хуже, чем никакого, потому что он стоит тебе времени на его опровержение.
После этого урока ты можешь назвать четыре семейства плохих комментариев (избыточные, устаревшие/вводящие в заблуждение, закомментированный код, баннерный шум) и удалять их с первого взгляда; сформулировать законные задачи, которые комментарий действительно решает — объяснить почему, а не что, предупредить о последствии, закодировать намерение, которое язык выразить не может, отметить TODO с тикетом; и распознать провал на другом полюсе: догму «никогда никаких комментариев», которая вырезает по-настоящему необходимое обоснование.
Избыточный комментарий повторяет код и добавляет вторую вещь, которую нужно держать в синхроне. Если комментарий говорит ровно то же, что и следующая строка, он не несёт информации — он лишь удваивает поверхность, способную разойтись.
// 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, комментарий теперь неверен, и ты сфабриковал ложь из того, что было просто бесполезным. Лечение не в том, чтобы написать лучший пересказ — а в том, чтобы удалить комментарий и, если строка была неясна, сделать яснее код (имя получше, функцию поменьше). Код — источник истины; не перефразируй его.
Устаревший комментарий — это баг без теста, который его поймал бы. Это опасное семейство, потому что оно не просто тратит взгляд — оно с полной уверенностью отправляет тебя по неверному пути.
// 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), которое ничто не обеспечивает, поэтому функция молча вернёт мусор в тот день, когда вызывающий о нём забудет. Комментарии вроде этих гниют именно потому, что сидят рядом с кодом, который меняется без них. Каждый оставленный комментарий — это бремя сопровождения, которое ты подписался обновлять вручную, вечно, и никакой компилятор тебе не напомнит.
Закомментированный код — мёртвый груз; удали его, система контроля версий помнит. Блок кода, «оставленный на всякий случай», обёрнутый в // или /* */, — один из самых частых и наименее оправданных комментариев.
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 хранят каждую строку, которую ты когда-либо удалял, вместе с коммитом, объясняющим почему. Удаление закомментированного кода ничего не теряет и убирает шум. Сохранение его ничего не сберегает и добавляет сомнение.
Хороший комментарий объясняет ПОЧЕМУ, а не ЧТО — единственное, что код по-настоящему сказать не может. Код превосходен в выражении того, что он делает и как. Чего он выразить не может — это рассуждение, которого нет в синтаксисе: ограничение, которое ты обходишь, последствие «безобидно» выглядящей правки, неочевидный выбор, сделанный намеренно.
// 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-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.