Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Kernigan_B__Payk_R_Praktika_programmirovania.pdf
Скачиваний:
76
Добавлен:
18.03.2016
Размер:
2.53 Mб
Скачать

Стиль

Имена

Выражения

Стилевое единство и идиомы

Макрофункции

Загадочные числа

Комментарии

Стоит ли так беспокоиться?

Дополнительная литература

Есть старое наблюдение, что лучшие писатели иногда пренебрегают правилами риторики. Однако, когда они это делают, читатель обычно находит в тексте какие-то компенсирующие достоинства, достигнутые ценой этого нарушения. Пока кто-то не уверен, что он сможет сделать то же самое, ему, вероятно, лучше всего следовать правилам.

Вильям Странк, Элвин Б. Уайт. Элементы стиля

Приводимый фрагмент кода взят из большой программы, написанной много лет назад:

if ( (country == SING) || (country == BRNI) || (country == POL) |[ (country

== ITALY) ) {

/*

*если страна - Сингапур, Бруней или Польша,

*то временем ответа является текущее время,

*а не время, передаваемое в параметре.

*Переустанавливается время ответа

*и задается день недели. */

...

Этот фрагмент тщательно написан, форматирован и комментирован, а программа, из которой он взят, работает предельно корректно; программисты, написавшие ее, по праву гордятся своим творением. Однако для постороннего глаза этот фрагмент все же представляется странноватым. По какому принципу объединены Сингапур, Бруней, Польша и Италия? И почему Италия не упомянута в комментариях? Код и комментарий не совпадают, так что один из них неверен. Не исключено, что неверны и оба. Правда, код все же исполнялся и тестировался, так что, скорее всего, он верен, а комментарий просто забыли обновить вместе с кодом. Комментарий не объясняет, что общего у перечисленных в нем стран, хотя эта информация будет просто необходима, если вам придется заниматься поддержкой этого кода.

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

Начнем мы с непривычного — с обсуждения стиля в программировании. Чем лучше у вас стиль, тем проще читать ваши программы вам самим и другим программистам; хороший стиль просто необходим для хорошего программиста. Мы начинаем со стиля, чтобы в дальнейшем вы обращали на него внимание во всех примерах.

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

Принципы хорошего стиля программирования состоят вовсе не в наборе каких-то обязательных правил, а в здравом смысле, исходящем из опыта. Код должен быть прост и понятен: очевидная логика, естественные выражения, использование соглашений, принятых в языке разработки, осмысленные имена, аккуратное форматирование, развернутые комментарии, а также отсутствие хитрых трюков и необычных конструкций. Логичность и связность необходимы, потому что другим будет проще читать код, написанный вами, а вам, соответственно, — их код, если все будут использовать один и тот же стиль. Детали могут быть продиктованы местными соглашениями, требованиями менеджмента или самой программой, но даже если это не так, то лучше всего подчиняться наиболее распространенным соглашениям. Мы в своем повествовании будем придерживаться стиля, использованного в книге "Язык программирования С" с некоторыми поправками для

C+ + и Java.

Правила, о которых пойдет речь, будут во множестве иллюстрироваться простыми примерами хорошего и плохого стиля, на контрасте все видно лучше. Кстати, примеры плохого стиля не придуманы специально; мы будем приводить куски кода из реальных программ, написанных самыми обычными программистами (даже нами самими), работавшими в обычной обстановке, когда работы много, а времени мало.

Некоторые куски будут несколько укорочены для большей выразительности, но не искажены. После "плохих" примеров мы будем приводить варианты, показывающие, как можно их улучшить. Так как все примеры взяты из реальных программ, со многими из РГИХ связано сразу несколько проблем. Обсуждение всех недостатков сразу уводило бы нас слишком далеко, так что некоторые примеры хорошего стиля будут по-прежнему таить в себе другие, неотмечаемые погрешности.

Для того чтобы вы могли без труда отличить хорошие примеры от плохих, мы будем начинать строки сомнительного кода со знака вопроса, как, например, в этом фрагменте (помните — все фрагменты взяты из реальных программ):

?«define ONE 1

?«define TEN 10

?«define TWENTY 20

Что может быть спорным в этих макроопределениях? А вы представьте себе изменения, которые необходимо будет внести в программу, если массив из TWENTY (двадцати) элементов понадобится увеличить. Нужно по меньшей мере заменить все имена на более осмысленные, указывающие на роль данного значения в программе:

#define INPUT_MODE 1

#«define INPUT_BUFSIZE 10

#«define OUTPUT_BUFSIZE 20

Имена

Что в имени? Имя переменной или функции помечает объект и содержит некоторую информацию о его назначении. Имя должно быть информативным, лаконичным, запоминающимся и, по возможности, произносимым. Многое становится ясным из контекста и области видимости переменной: чем больше область видимости, тем более информативным должно быть имя.

Используйте осмысленные имена для глобальных переменных и короткие — для локальных. Глобальные переменные по определению могут проявиться в любом месте программы, поэтому их имена должны быть достаточно длинными и информативными, чтобы напомнить читателю об их предназначении. Полезно описание каждой глобальной переменной снабжать коротким комментарием:

int npending = 0; // текущая длина очереди ввода

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

Для локальных переменных, наоборот, лучше подходят короткие имена; для использования внутри функции вполне сойдет просто п, неплохо будет смотреться npoints, а вот numberO.f Points будет явным перебором.

Обычно используемые локальные переменные по соглашению могут иметь очень короткие имена. Так, употребление i и j для обозначения счетчиков цикла, р и q для указателей, s и t для строк стало настолько привычным, что применение более длинных имен не принесет никакой пользы, а наоборот, может даже навредить. Сравните

for (theElementlndex = 0; theElementlndex < numberOfElements; ? theElementIndex-н-)

? elementArrayftheElementlndex] = theElementlndex;

и

for (i = 0; i < nelems; i++) elem[i] = i;

Бывает, что программисты используют длинные имена независимо от контекста. Это ошибка: ясность часто достигается краткостью.

Существует множество соглашений и местных традиций именования. К наиболее распространенным правилам относятся использование для указателей имен, начинающихся или заканчивающихся на р (от pointer, указатель), например nodep, а также заглавных букв в начале имен Глобальных переменных и всех заглавных — в

именах КОНСТАНТ. Некоторые программистские фирмы придерживаются более радикальных правил, таких как отображение в имени переменной информации об ее типе и способе использования. Например, реп будет означать указатель на символьный тип (pointer to char), a st rFrom и strTo — строки для ввода и вывода, соответственно. Что же касается собственно напи-. сания имен, то.использование npending, numPending или num_pending зависит от личных пристрастий, и

принципиальной разницы нет. Главное — соблюдать основные смысловые соглашения; все, что не выходит за их рамки, уже не так важно.

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

Пространства имен (namespaces) в C++ и пакеты (packages) в Java позволяют разграничивать области видимости переменных и тем самым помогают сохранить ясность кода без использования чрезмерно длинных имен.

Будьте последовательны. Сходным объектам давайте сходные имена, которые бы показывали их сходство и подчеркивали различия между ними.

Вот пример на языке Java, где имена членов класса не только слишком длинны, но и абсолютно непоследовательны:

?class UserQueue { .

?int noOfltemsInQ, f rontOfTheQueue, queueCapacity;

?public int noOfllsersInQueueO {....}

Слово "queue" (очередь) используется в названиях как Q, Queue и queue. Однако, поскольку доступ к очереди может быть получен только через переменную типа UserQueue, в именах членов класса упоминание слова "queue" вообще не нужно — все будет понятно из контекста. Так, двойное упоминание очереди

? queue. queueCapacity

избыточно.Класс лучше описать так:

class UserQueue {

int nitems, front, capacity; publicint nusers( ) { . . . }

поскольку выражения вроде

queue.capacity+++;

n=queue.nusers()

вполне ясны и понятны. Последняя версия тоже еще не идеальна: термины "items" и "users" обозначают одно и то же, так что для одного понятия следовало бы использовать только один термин.

Используйте активные имена для функций. Имя функции должно базироваться на активном глаголе (в действительном залоге), за которым может следовать существительное:

now = date.getTime(); putchar( "\n-);

Функции, которые возвращают логическое значение (истина или ложь — t rue или false), нужно называть так, чтобы их смысл не вызывал сомнений. Так, из вызова

? if (checkoctal(c)) ...

непонятно, когда будет возвращаться t rue, а когда false, а вот вызов

if (isoctal(c)) ...

не оставляет сомнений в том, что результат будет истиной, если аргумент — восьмеричное число, и ложью — в противном случае.

Будьте точны. Как мы уже решили, имя не только обозначает объект, но и предоставляет читателю некоторую информацию о нем. Неверно подобранное имя может стать причиной совершенно таинственных ошибок.

Один из нас написал и не один год распространял макрос, называемый isoctal, имеющий некорректную реализацию:

«define isoctal(c) ((с) >= '0' && (с) <= '8')?

вместо правильного

«define isoctal(c) ((c)>= '0' && (с) <= '7')

Вэтом случае имя было правильным, а реализация ошибочной; правильное имя облегчило раскрытие ошибки.

Вследующем примере имя и код находятся в полнейшем противоречии:

?public boolean inTable(Object obj) {

?int j = this.getlndex(ob]);

?return (j == nTable);

?}

Функция getlndex возвращает значение от 0 до nTable-1 в случае, если объект найден, и nTable, если объект не найден. Таким образом, возвращаемое логическое значение будет прямо противоположно тому, что подразумевается в имени функции. Во время написания кода это, может быть, не страшно, но, если программа будет дорабатываться позднее, да еще другим программистом, подобное неправильное имя наверняка станет причиной затруднений.

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

Составьте комментарий, объясняющий принцип выбора имен и значений в следующем примере:

?«define TRUE О ? «define FALSE 1

?if ((ch = getcharO) == EOF)

?not_eof = FALSE;

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