- •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.5. Загадочные числа
Загадочные числа — это константы, размеры массивов, позиции символов и другие числовые значения, появляющиеся в программе непосредственно, как "буквальные константы".
Давайте имена загадочным числам. В принципе нужно считать, что любое встречающееся в программе число, отличное от 0 и 1, является загадочным и должно получить собственное имя. Просто "сырые" числа в тексте программы не дают представления об их происхождении и назначении, затрудняя понимание и изменение программы. Вот отрывок из программы, которая печатает гистограмму частот букв на терминале с разрешением 24 X 80. Этот отрывок неоправданно запутан из-за целого сонма непонятных чисел:
? fac = lim / 20; /* установка масштаба */
? if (fac < 1)
? fac = 1;
? /* генерация гистограммы */
? for (i = 0, col = 0; i < 27; i++, j++) {
? col += 3;
? k = 21 - (let[i] / fac);
? star = (let[i] == 0) ? ' ' : ' * ' ;
? for (j = k; j < 22; j++)
? draw(j, col, star);
? }
? draw(23, 2, ' '); /* разметка оси х */
? for (i = 'A'; i< ‘Z’ ; i++)
? printf("%c” , i ) ;
Наряду с другими в коде присутствуют числа 20, 21, 22, 23 и 27. Они как-то тесно связаны... или нет? На самом деле есть только три критических числа, существенных для программы: 24 — число строк экрана; 80 — число столбцов экрана и, наконец, 26 — количество букв в английском алфавите. Однако, как мы видим, ни одно из этих чисел в коде не встречается, отчего числа, используемые в коде, становятся еще более загадочными.
Присвоив имена числам, имеющим принципиальное значение, мы облегчим понимание кода. Например, станет понятно, что число 3 берется из арифметического выражения (80-1)/26, а массив let должен иметь 26 элементов, а не 27 (иначе возможна ошибка его переполнения на единицу — из-за того, что экранные координаты индексируются, начиная с 1). Сделав еще пару усовершенствований, мы придем к следующему результату:
enum {
MINROW = 1, /* верхняя граница,*/
MINCOL = 1, /* левая граница */
MAXROW = 24, /* нижняя граница (<=) */
MAXCOL = 80, /* правая граница (<=) */
LABELROW = 1, /* позиция подписей оси */
NLET = 26, /* количество букв алфавита*/
HEIGHT = MAXROW – 4, /* высота столбиков */
WIDTH = (MAXCOL-1 / NLET ) /*- ширина столбиков */
};
fac = (lim + HEIGHT-1) / HEIGHT; /* установка масштаба */
if (fac < 1)
fac = 1;
for (i = 0; i < NLET; i++) { /* генерация гистограммы */
if (let[i] == 0)
continue;
for (j = HEIGHT - let[i]/fac; j < HEIGHT; j++)
draw(j+1 + LABELROW, (i+1)*WIDTH, ' * ');
}
draw(MAXROW-1, MINCOL+1, ' '); /* разметка оси х */
for (i = 'A'; i <= 'Z'; i++)
printf("%c ", i);
Теперь стало гораздо понятнее, что же делает основной цикл: в нем используется стандартный идиоматический цикл от 0 до NLET-1, то есть по всем элементам данных. Вызовы функции d raw также стали более понятны — названия вроде MAXROW и MINCOL дают четкое представление об аргументе. Главное же — программу теперь можно без труда адаптировать к другому разрешению экрана или другому алфавиту: с чисел и с кода снята завеса таинственности.
Определяйте числа как константы, а не как макросы. Программисты, пишущие на С, традиционно использовали для определения загадочных чисел директиву ffdefine. Однако препроцессор С— мощный, но несколько туповатый инструмент, а макросы — вообще довольно опасная вещь, поскольку они изменяют лексическую структуру программы. Пусть лучше язык делает свойственную ему работу. В С и C++ целые (integer) константы можно определять с помощью выражения en urn (которое мы и использовали в предыдущем примере). В C++ любые константы можно определять с помощью ключевого слова const:
const int MAXROW = 24, MAXCOL = 80;
В Java для этого служит слово final:
static final int MAXROW = 24, MAXCOL = 80;
В С также можно определять значения с помощью ключевого слова const, но эти значения нельзя использовать как границы массива, так что зачастую придется прибегать все к тому же enum.
Используйте символьные, а не целые константы. Для проверки свойств символов должны использоваться функции из <ctype . h> или их эквиваленты. Если проверку организовать так:
? if (с >= 65 && с <= 90)
? ..........
то ее результат будет всецело зависеть от представления символов на конкретной машине. Лучше использовать
? if (с >= 'А' && с <= ‘Z' )
? ...........
но и это может не принести желаемого результата, если буквы в имеющейся кодировке идут не по порядку или если в алфавите есть и другие буквы. Лучшее решение — привлечь на помощь библиотеку:
if (isupper(c))
........
в С и C++ или
if (Character.isUpperCase(c))
............
в Java.
Сходный вопрос — использование в программе числа 0. Оно используется очень часто и в различных контекстах. Компилятор преобразует это число в соответствующий тип, однако читателю гораздо проще понять роль каждого конкретного 0, если тип этого числа каждый раз обозначен явным образом. Так, например, стоит использовать (void * )0 или NULL для обозначения нулевого указателя в С, а ' \0' вместо просто 0 — для обозначения нулевого байта в конце строки. Другими словами, не пишите
? str = 0;
? name[i] = 0;
? х = 0;
а пишите
str = NULL;
name[i] = ‘\0’;
x = 0.0;
Мы рекомендуем использовать различные явные константы, оставив О для простого целого нуля, — такие константы обозначат цель использования данного значения. Надо отметить, правда, что в C++ для обозначения нулевого указателя принято использовать все же 0, а не NULL. Лучше всего проблема решена в Java — там ключевое слово null определено как ссылка на объект, которая ни к чему не относится.
Используйте средства языка для определения размера объекта. Не используйте явно заданного размера ни для каких типов данных — так, например, используйте sizeof (int) вместо прямого указания числа 2,4 и т.п. По сходным причинам лучше использовать sizeof(аггау[0]) вместо sizeof (int) — меньше придется исправлять при изменении типа массива. Использование оператора sizeof избавит вас от необходимости выдумывать имена для чисел, обозначающих размер массива. Например, если написать
char buf[1024];
fgets(buf, sizeof(buf), stdin);
то размер буфера хоть и станет "загадочным числом", от которого мы предостерегали ранее, но зато оно появится только один раз — непосредственно в описании. Может быть, и не стоит прилагать слишком большие усилия, чтобы придумать имя для размера локального массива, но определенно стоит постараться и написать код, который не нужно переписывать при изменении размера или типа.
У массивов в Java есть поле length, которое содержит количество элементов:
char buf[] = new char[1024];
for (int i = 0; i < buf.length; i++)
............
В С и C++ нет эквивалента этому полю, но для массива (не указателя), описание которого является видимым, количество элементов можно вычислить с помощью следующего макроса:
#define NELEMS(array) (sizeof(array) / sizeof(array[0]))
double dbuf [100];
for (i = 0; i < NELEMS(dbuf); i++)
............
Здесь опять-таки размер массива задается лишь в одном месте, и при его изменении весь остальной код менять не придется.
В данном макросе нет проблем с многократным вычислением аргумента, поскольку в нем нет никаких побочных эффектов, и на самом деле все вычисление происходит во время компиляции. Это пример грамотного использования макроса — здесь он делает то, чего не может сделать функция: вычисляет размер массива исходя из его описания.
Упражнение 1-10
Как бы вы переписали приведенные определения, чтобы уменьшить число потенциальных ошибок?
? #define FT2METER 0.3048
? #define METER2FT 3.28084
? #define MI2FT 5280.0
? #define MI2KM 1.609344
? #define SQMI2SQKM 2.589988
