Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
kernigan_paik.doc
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
2.91 Mб
Скачать

1.6. Комментарии

Комментарии должны помогать читать программу. Они не помогут, | повторяя то, что и так понятно из кода или противореча коду или от­влекая читателя от сути типографскими ухищрениями. Лучший ком-I. ментарий помогает понимать программу, кратко отмечая наиболее I значимые детали или предоставляя укрупненную картину происходя­щего.

Не пишите об очевидном. Комментарии не должны содержать самооче-fc видной информации типа того, что оператор i++ увеличивает i. Ниже приведены некоторые из наших чемпионов бессмысленности:

? /*

? * поумолчанию

? */

? default:

? break;

? /*возвращаем SUCCESS */

? return SUCCESS;

? zerocount++; /* Увеличиваем счетчик нулевых элементов */

? /* Устанавливаем "total" в "number_received" */

? node->total = node->number_received;

Все эти комментарии надо удалить, они лишь мешают.

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

? while ((с = getchar()) != EOF && isspace(c))

? ; /* пропуск пробелов */

? if (c == EOF) /* конец файла */

? type = endoffile;

? else if (c == ' ( ' ) /* открывающая скобка */

? type = leftparen;

? else if (c == ')') /* закрывающая скобка */

? type = rightparen;

? else if (c == ';') /* точка с запятой */

? type = semicolon;

? else if (is_op(c)) /* оператор */

? type = operator;

? else if (isdigit(c)) /* цифра */

? ..........

Эти комментарии также стоит удалить, поскольку грамотно подобран­ные имена уже содержат всю необходимую информацию.

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

Глобальные переменные имеют тенденцию появляться в разных мес­тах программы, поэтому иногда их стоит сопроводить комментарием с напоминанием об их роли. В качестве примера мы выбрали отрывок программы, приведенной в главе 3 этой книги:

struct State { /* префикс + список суффиксов */

char *pref[NPREF]; /* префиксы */

Suffix *suf; /* список суффиксов */

State *next; }; /* следующий в хэш-таблице */

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

// random: возвращает случайное целое в диапазоне [0..r-1].

int random(int r)

{

return (int)(Math.floor(Math.random()*r));

}

Иногда код действительно сложен, возможно, из-за сложного алго­ритма или замысловатых структур данных. В таком случае читателю может помочь комментарий, указывающий на источник, в котором опи­сан данный алгоритм или структура. В него можно также включить по­яснения, касающиеся того, почему были приняты те или иные конкрет­ные решения. Ниже приведен комментарий, предваряющий весьма эффективную реализацию обратного дискретного косинус-преобразо­вания (discrete cosine transform - DCT), используемого в декодере изображений в формате JPEG:

/*

* idct: масштабированная целочисленная реализация

* обратного двумерного (8x8) дискретного

* косинус-преобразования (DСТ).

* Алгоритм Чен-Ванга (Chen-Wang)

* (IEEE ASSP-32, с. 803-816, август 1984)

*

* 32-битовая целочисленная арифметика 8-битовые коэффициенты

* 11 умножений, 29 сложений на одно DCT

*

* Коэффициенты увеличены до 12 битов для совместимости

* с IEEE 1180-1990

* static void idct(int b[8*8])

{

........

}

В этом содержательном комментарии имеется ссылка на описание алго­ритма, кратко представлены используемые данные, обозначена произво­дительность алгоритма и показано, когда и для чего была произведена модификация кода.

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

? /* Если result = 0 , то было найдено совпадение,

? поэтому возвращаем true (не 0)

? В противном случае result не равен нулю,

? поэтому возвращаем false (ноль). */

?

? #ifdef DEBUG

? printf (“*** isword возвращает ! result = %d\n, ! result);

? fflush (stdout);

? #endif

?

? return (!result);

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

#ifdef DEBUG

printf("*** isword возвращает matchfound = %d\n", matchfound);

fflush(stdout);

#endif

return matchfound;

He противоречьте коду. Как правило, все комментарии согласуются с кодом, когда он пишется, но по мере устранения ошибок и развития программы комментарии часто остаются без изменений и перестают со­ответствовать коду. Скорее всего, именно из-за этого и возникли про­блемы во фрагменте, приведенном в самом начале этой главы.

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

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

? time(&now);

? strcpy(date, ctime(&now));

? /* избавляемся от замыкающего символа перевода строки,

? скопированного из ctime */

? i = 0;

? while(date[i] >= ' ') i++;

? date[i] = 0;

Первое, что надо сделать, это переписать код в более привычном идио­матическом виде:

? time(&now);

? strcpy(date, ctime(&now));

? /* избавляемся от замыкающего символа перевода строки,

? скопированного из ctime */

? for (i = 0; date[i] != '\n'; i++)

? ;

? date[i] = ‘\0’;

Теперь код и комментарий соответствуют друг другу, но и тот и другой! можно улучшить, сделав их более прямолинейными. Задача фрагмента — удалить символ перевода строки, который функция ctime помещае в конец возвращаемой ею строки. Собственно, это комментарий и должен сообщить, а код сделать:

time(&now);

strcpy(date, ctime(&now));

/* ctime() помещает символ перевода строки в конец

возвращаемой строки; его надо удалить */

date[strlen(date)-1] = ‘\0’;

Последнее выражение — идиома языка С, предназначенная для удале ния последнего символа из строки. Теперь наш код стал коротким, стан дартизованным и понятным, а комментарий поясняет причину, по кото рой в этом коде возникла необходимость.

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

? int strcmp(char *s1, char *s2)

? /* процедура сравнения строк возвращает -1, если */

? /* s1 стоит выше s2 в списке по возрастанию, */

? /* 0, если строки равны, и 1, если s1 ниже s2 */

? {

? while(*s1==*s2) {

? if(*s1=='\0') return(0);

? s1++;

? s2++;

? }

? if(*s1>*s2) return(1);

? return(-1);

? }

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

/* strcmp: возвращает < 0 если s1<s2,

> 0 если s1>s2

и 0 если s1=s2 */

/* ANSI С, раздел 4.11.4.2 */

int strcmp(const char *s1, const char *s2)

{

..........

}

Студентам внушают, что комментировать надо все подряд; профессио­нальным программистам часто вменяется в обязанность комментировать весь созданный ими код. Однако если слепо следовать правилам, то смысл написания комментариев может потеряться. Комментарии должны помо­гать читателю разобраться в тех частях программы, смысл и назначение которых нельзя (или просто трудно) понять из самого кода. Всюду, где это возможно, старайтесь писать код, который было бы просто понять; чем лучше вам это удастся, тем меньше комментариев вам потребуется. Хоро­ший код меньше нуждается в комментариях, чем плохой.

Упражнение 1-11

Прокомментируйте эти комментарии.

? void diet :: insert(string& w)

? // возвращает 1, если w в словаре, в противном случае – 0

? if (n > МАХ || n % 2 > 0) // проверка на четность

? // Пишем сообщение

? // Увеличить счетчик строк для каждой написанной строки

?

? void write_message()

? {

? // увеличить счетчик строк

? line_number = line_number + 1;

? fprintf(fout, "%d %s\n%d %s\n%d %s\n",

? line_number, HEADER,

? line_number + 1, BODY,

? line_number + 2, TRAILER);

? // увеличить счетчик строк

? line_number = line_number + 2;

? }

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]