- •1. Стиль 10
- •3. Проектирование и реализация 63
- •4. Интерфейсы 85
- •5. Отладка 115
- •6. Тестирование 134
- •7. Производительность 157
- •8. Переносимость 180
- •9. Нотация 203
- •Введение
- •Брайан в. Керниган
- •1.1. Имена
- •1.2. Выражения
- •Упражнение 1 -6
- •1.3. Стилевое единство и идиомы
- •1.4. Макрофункции
- •1.5. Загадочные числа
- •1.6. Комментарии
- •1.7. Стоит ли так беспокоиться?
- •Дополнительная литература
- •2.1. Поиск
- •2.2. Сортировка
- •2.3. Библиотеки
- •2.4. Быстрая сортировка на языке Java
- •2.5. "О большое"
- •2.6. Динамически расширяемые массивы
- •2.7. Списки
- •Упражнение 2-8
- •2.8. Деревья
- •Упражнение 2-15
- •2.10. Заключение
- •Дополнительная литература
- •Проектирование и реализация
- •3.1. Алгоритм цепей Маркова
- •3.2. Варианты структуры данных
- •3.3. Создание структуры данных в языке с
- •3.4. Генерация вывода
- •3.5.Java
- •Into the air. When water goes into the air it
- •3.7. Awk и Perl
- •3.8. Производительность
- •3.9. Уроки
- •Дополнительная литература
- •4. Интерфейсы
- •4.1. Значения, разделенные запятой
- •4.2. Прототип библиотеки
- •4.3. Библиотека для распространения
- •Упражнение 4-4
- •4.5 Принципы интерфейса
- •4.6. Управление ресурсами
- •4.7. Abort, Retry, Fail?
- •4.8. Пользовательские интерфейсы
- •Дополнительная литература
- •5. Отладка
- •5.1. Отладчики
- •5.2. Хорошие подсказки, простые ошибки
- •5.3, Трудные ошибки, нет зацепок
- •5.4. Последняя надежда
- •5.5. Невоспроизводимые ошибки
- •5.6. Средства отладки
- •5.7. Чужие ошибки
- •5.8. Заключение
- •Дополнительная литература
- •6. Тестирование
- •6.1. Тестируйте при написании кода
- •6.2. Систематическое тестирование
- •6.3. Автоматизация тестирования
- •6.4. Тестовые оснастки
- •6.5. Стрессовое тестирование
- •6.6. Полезные советы
- •6.7. Кто осуществляет тестирование?
- •6.8. Тестирование программы markov
- •6.9. Заключение
- •Дополнительная литература
- •7.Производительность
- •7.1. Узкое место
- •7.2. Замеры времени и профилирование
- •7.3. Стратегии ускорения
- •7.4. Настройка кода
- •7.5. Эффективное использование памяти
- •7.6. Предварительная оценка
- •7.7. Заключение
- •Дополнительная литература
- •8. Переносимость
- •8.1. Язык
- •8.2. Заголовочные файлы и библиотеки
- •8.3. Организация программы
- •8.4. Изоляция
- •8.5. Обмен данными
- •8.6. Порядок байтов
- •8.7. Переносимость и внесение усовершенствований
- •8.8. Интернационализация
- •8.9. Заключение
- •Дополнительная литература
- •9.1. Форматирование данных
- •9.2. Регулярные выражения
- •Упражнение 9-12
- •9.3. Программируемые инструменты
- •9.4. Интерпретаторы, компиляторы и виртуальные машины
- •9.5. Программы, которые пишут программы
- •9.6. Использование макросов для генерации кода
- •9.7. Компиляция "налету"
- •Дополнительная литература
- •Интерфейсы
- •Отладка
- •Тестирование
- •Производительность
- •Переносимость
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;
? }
