Към съдържанието
wannacode

Коментари, които лъжат, и такива, които помагат

Накратко: Добрият коментар казва защо кодът е такъв; коментар, който преразказва кода, остарява и започва да лъже, затова се заменя с добро име или функция.

Проблем

Коментарът, на който всички вярват

Магазинът дава безплатна доставка от определена сума нагоре. Миналата пролет прагът беше 50 € и коментарът над deliveryFee казва точно това. После маркетингът го вдигна на 100 €. Числото в кода е сменено. Коментарът не е.

Тази есен колега пише банера в кошницата. Чете коментара, не кода, и слага 50:

// Безплатна доставка за поръчки от 50 € нагоре.
function deliveryFee(total) {
if (total >= 100) {
return 0;
}
return 4.9;
}
function banner(total) {
if (total >= 50) {
return "Доставката е безплатна!";
}
return `Още ${(50 - total).toFixed(2)} € до безплатна доставка`;
}
const total = 60;
console.log(`Кошница: ${total.toFixed(2)} €`);
console.log(banner(total));
console.log(`Доставка: ${deliveryFee(total).toFixed(2)} €`);
Изход
Кошница: 60.00 €
Доставката е безплатна!
Доставка: 4.90 €

Клиент с кошница за 60 € чете „Доставката е безплатна!“ и после плаща 4,90 € за доставка. Поддръжката гледа deliveryFee и не намира грешка: функцията прави точно каквото пише в кода. Грешката е в текст, който никой не пуска.

Виж го

Кодът продължи, коментарът остана

Ето какво стана, стъпка по стъпка:

// Безплатна доставка за поръчки от 50 € нагоре.
function deliveryFee(total) {
if (total >= 100) {
return 0;
}
return 4.9;
}
function banner(total) {
if (total >= 50) {
return "Доставката е безплатна!";
}
return `Още ${(50 - total).toFixed(2)} € до безплатна доставка`;
}
const total = 60;
console.log(`Кошница: ${total.toFixed(2)} €`);
console.log(banner(total));
console.log(`Доставка: ${deliveryFee(total).toFixed(2)} €`);
Стъпка по стъпка, с думи
  1. Първата версия: коментарът (ред 1) и проверката (ред 3) казват едно и също: 50 €.
  2. Прагът става 100 €. Някой сменя числото на ред 3, а коментара не го пипа. Компилаторът проверява кода. Коментарите не ги проверява никой.
  3. Колега пише банера. Чете коментара, не кода, и на ред 10 слага 50.
  4. Кошница за 60 €. Банерът пита своя праг: 60 ≥ 50, значи „Доставката е безплатна!“.
  5. Таксата пита истинския праг: 60 < 100, значи доставката е 4,90 €.
  6. Клиентът чете „безплатна“ и плаща 4,90 €. Лъжата е на ред 1. Бъгът, който тя роди, е на ред 10.

Компилаторът проверява всеки ред код. Тестовете проверяват какво прави кодът. Коментара не го проверява никой: той е текст до кода, и при всяка промяна на кода може да остане назад. Такъв коментар се казва остарял коментар (stale comment). Той е по-лош от липсващ, защото звучи уверено.

Идеята

Нека кодът го каже сам

Прагът е правило на магазина, а правилото е на две места: в кода на таксата и в главата на всеки, който е чел коментара. Дай му име, и двете функции питат името:

const FREE_DELIVERY_FROM = 100;
function deliveryFee(total) {
if (total >= FREE_DELIVERY_FROM) {
return 0;
}
return 4.9;
}
function banner(total) {
if (total >= FREE_DELIVERY_FROM) {
return "Доставката е безплатна!";
}
return `Още ${(FREE_DELIVERY_FROM - total).toFixed(2)} € до безплатна доставка`;
}
const total = 60;
console.log(`Кошница: ${total.toFixed(2)} €`);
console.log(banner(total));
console.log(`Доставка: ${deliveryFee(total).toFixed(2)} €`);
Изход
Кошница: 60.00 €
Още 40.00 € до безплатна доставка
Доставка: 4.90 €

Коментарът вече не трябва: името казва същото. Разликата е, че името не може да остане назад. Ако прагът стане 150 €, сменяш един ред и banner получава новия праг. Ако някой изтрие константата, програмата спира да се компилира (в JavaScript спира при първото извикване с грешка, че името го няма).

Това е правилото на урока за имената (Имена в кода): преди да напишеш коментар, провери дали име не може да каже същото.

Пробвай

Предвиди изхода

Хотелът продава нощувки онлайн. Какво печата програмата?

// Цената с ДДС 20%.
function withVat(net) {
return net * 1.09;
}
console.log(`${withVat(100).toFixed(2)} €`);
Отговор

Коментарът казва 20%, а кодът умножава по 1.09. Нощувките в България са с ДДС 9%, и някой е сменил числото, без да пипне коментара. Програмата слуша кода.

Виж го

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

Всеки коментар тук казва същото като реда под него:

// Цената на продукта.
const price = 25;
// Количеството.
const quantity = 3;
// Умножаваме цената по количеството.
const total = price * quantity;
// Отпечатваме сумата.
console.log(`Общо: ${total.toFixed(2)} €`);
Изход
Общо: 75.00 €

Четеш всичко два пъти и не научаваш нищо ново. По-лошото е, че свикваш да прескачаш коментарите, и прескачаш и онзи, който е важен. Изтрий ги. Програмата е същата, а изходът също.

Виж го

Коментар вместо име, коментар вместо функция

Тук коментарите обясняват неща, които кодът крие:

const n = 7; // брой поръчки на клиента
const s = 420; // оборот на клиента в евро
// Клиентът е VIP: поне 5 поръчки или поне 500 € оборот.
if (n >= 5 || s >= 500) {
console.log("VIP отстъпка: 10%");
}
Изход
VIP отстъпка: 10%

Коментарът до n е там, защото името не казва нищо. Дай му името от коментара: orders. Коментарът над if описва правило. Правилото с име е функция:

function isVip(orders, spent) {
return orders >= 5 || spent >= 500;
}
const orders = 7;
const spent = 420;
if (isVip(orders, spent)) {
console.log("VIP отстъпка: 10%");
}
Изход
VIP отстъпка: 10%

Изходът е същият, а коментари няма. Редът if се чете като изречение. Ако правилото стане „поне 10 поръчки“, сменяш тялото на isVip и всяко място, което я вика, получава новото правило. Как се отделя такава функция, е в урока Функции.

Виж го

Закоментиран код и TODO без собственик

function confirm(orderId) {
sendEmail(orderId);
// sendSms(orderId);
// if (smsEnabled) {
// sendSms(orderId);
// }
// TODO: оправи
}
Изход
Имейл за поръчка #1063

Изпраща ли се SMS? Никой не знае дали е спрян нарочно, временно или по грешка, и никой не смее да го изтрие. Така закоментираният код живее години. Изтрий го: старият код е в историята на git и се връща с една команда.

TODO без име и без срок е желание, което никой не притежава. Добрият TODO казва кой, кой тикет и кога:

function confirm(orderId) {
sendEmail(orderId);
// TODO(maria, SHOP-412): добави SMS, когато новият доставчик мине тестовете.
}
Изход
Имейл за поръчка #1063

Още по-добре е задачата да е в тикета, а в кода да остане само връзката.

Идеята

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

Кодът казва какво прави. Добрият коментар казва нещо, което кодът не може да каже:

// Куриерът взима 30% от таксата, закръглено надолу до цент.
// Така е в договора с него (т. 4.2), затова тук няма Math.round.
function courierShareCents(feeCents) {
return Math.floor((feeCents * 30) / 100);
}
const fee = 495; // SHOP-388: куриерът праща таксата в центове, не в евро.
console.log(`Куриер: ${(courierShareCents(fee) / 100).toFixed(2)} €`);
Изход
Куриер: 1.48 €

Тук има три такива неща:

  • защо: закръгляне надолу изглежда като грешка, докато не прочетеш, че така е в договора. Без коментара някой ще го „поправи“;
  • къде да научиш повече: тикетът SHOP-388 разказва историята, която в кода не се събира;
  • мерната единица, която типът не казва: центове, не евро. В урока за миризмите ще видиш как това отива в самия тип.

Още едно: предупреждение като „Бавно: всяко извикване е заявка към банката, около 300 ms. Не го викай в цикъл.“ Такъв коментар пести на следващия човек един ден в търсене.

И добрият коментар може да остарее. Но остарява рядко, защото не повтаря кода: договорът не се сменя всеки път, когато сменяш функцията.

По-точно, за любопитните

git blame показва кога е писан всеки ред. Ако кодът под коментара е по-нов от самия коментар, бъди нащрек: някой е пипнал кода и може да не е стигнал до коментара.

Виж го

Документация за този, който вика

Който вика deliveryFee от друг модул, не иска да чете тялото ѝ. Иска да знае какво да подаде и какво ще получи. За това всеки език има документационен коментар, който редакторът показва, когато задържиш мишката над името:

const FREE_DELIVERY_FROM = 100;
/**
* Таксата за доставка на поръчка.
* @param {number} total сумата на стоките в евро, без доставката
* @returns {number} 0 от FREE_DELIVERY_FROM нагоре, иначе таксата на куриера
*/
function deliveryFee(total) {
if (total >= FREE_DELIVERY_FROM) {
return 0;
}
return 4.9;
}
console.log(`Доставка при 60 €: ${deliveryFee(60).toFixed(2)} €`);
console.log(`Доставка при 120 €: ${deliveryFee(120).toFixed(2)} €`);
Изход
Доставка при 60 €: 4.90 €
Доставка при 120 €: 0.00 €
Това е JSDoc: /** … */ над функцията, с @param {number} за всеки параметър и @returns за резултата. Node изобщо не го чете. Редакторът (VS Code) го показва при всяко извикване, а с ред // @ts-check в началото на файла проверява и типовете от {number}.

Документацията казва договора: какво значи параметърът и в какви единици е, какво се връща и кога има грешка. Не казва как е направено отвътре. Числата от кода не се повтарят в нея: вместо „от 100 €“ пише името на константата, за да не остане назад.

По-точно, за любопитните
С // @ts-check редакторът подчертава deliveryFee("60"), защото JSDoc казва {number}. Това е същата проверка, която TypeScript прави, без да сменяш езика на файла.

Попълни липсващото:

const FREE_DELIVERY_FROM = 100;

/**
 * Таксата за доставка на поръчка.
 * @param {} total сумата на стоките в евро, без доставката
 * @returns {number} 0 от FREE_DELIVERY_FROM нагоре, иначе таксата на куриера
 */
function deliveryFee(total) {
  if (total >= FREE_DELIVERY_FROM) {
    return 0;
  }
  return 4.9;
}

Пробвай

Намери грешката

Магазинът праща имейл „Как ти се стори поръчката?“ ден след доставката. Последната поръчка на клиента е от 24.10, а имейлът пита за тази от 12.10:

const orders = ["2026-10-19 #1057", "2026-10-24 #1063", "2026-10-12 #1041"];
console.log(reminder(orders));
Изход
Как ти се стори поръчка 2026-10-12 #1041?

Кой ред взима грешната поръчка?

Избери реда с грешката.

Упражнение

Изчисти коментарите

Реши задачата в редактора си.

Отстъпка за лоялни клиенти. Във функцията има коментар, който лъже, коментар, който повтаря кода, закоментиран ред и TODO без собственик:

// Отстъпка за лоялни клиенти: 5%.
function loyaltyTotal(total, orders) {
// ако клиентът има повече от 10 поръчки
if (orders >= 20) {
// total = total * 0.95;
total = total * 0.9; // намаляваме total
}
// TODO: закръгляне
return total;
}
console.log(`Сума: ${loyaltyTotal(200, 25).toFixed(2)} €`);
console.log(`Сума: ${loyaltyTotal(200, 12).toFixed(2)} €`);
Изход
Сума: 180.00 €
Сума: 200.00 €

Изчисти я: махни шума и мъртвия код, замени числата с имена, а TODO или направи, или му дай собственик и тикет. Изходът трябва да остане същият.

Още задачи

  • Документирай подредбата. Напиши документационен коментар за oldestFirst от задачата с поръчките: какво приема, какво връща и в какъв ред.
  • Банерът на 99,99 €. Пусни банера от картата „Нека кодът го каже сам“ с кошница за 99,99 € и за 100 €. Провери, че двете функции са съгласни и на самия праг.
  • Твоят проект. Отвори последния си проект и намери три коментара. За всеки реши: повтаря ли кода, казва ли защо, или лъже?

Тест

Провери се

1. Коментарът над функцията казва едно, а кодът под него прави друго. На кое вярваш?
2. Какво не е наред с „count++; // увеличава count с 1“?
3. Във функцията има закоментиран ред „// sendSms(order);“. Какво правиш?
4. Кой коментар си заслужава?
5. Кой TODO ще бъде направен някога?

Създаден от Иво Гергов като част от кампанията на Evo Tech за достъпно обучение по програмиране ·