Жанр: Учеба

Правила программирования на Си и Си плюс плюс.

сторінка №5

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

some_function( int, int, int, int, int );

то вам не удастся получить много помощи.
35. Используйте "предикатную" форму при
разбиении длинных выражений
"Предикатом" в английском языке называется вторая половина
предложения - глагол и дополнение, над которым глагол выполняет
действие. Порядок слов в английском предложении, конечно,
фиксированный: глагол всегда идет первым.
Многие компьютерные языки имитируют структуру английского языка
(языки Си и Си++ попадают в эту категорию). Паскаль, например, даже
повторяет пунктуацию английского предложения: с точкой с запятой,
отделяющей независимые операторы, и точкой на конце. Вызов функции
является хорошим примером предикатной формы: глагол является
именем функции, а прямые дополнения (вещи, на которые действует
глагол) являются аргументами.
Вы также можете посмотреть на операторы типа глагола, потому что
они выполняют некоторое действие над операндами ("объектами"). Это
разумно, поэтому используйте ту же самую схему, что и в английском
предложении, если вам нужно разбить длинное выражение на несколько
строк. Помещайте сначала глагол (оператор):
if
( its_thursday_and_the_moon_is_in_scorpio()
||its_friday_afternoon_and_close_to_quitting_time()
||i_just_cant_bear_to_look_at_this_computer_for_another_minute()
)
{
go_home();
}


Правила программирования на Си и Си++

48


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

if
( its_thursday_and_the_moon_is_in_scorpio()
||its_friday_afternoon_and_close_to_quitting_time()
||i_just_cant_bear_to_look_at_this_computer_for_another_minute())
go_home();

36. Подпрограмма должна помещаться на экране
Затраты на вызов подпрограмм в Си/Си++ невелики; если для функции
указано ключевое слово inline, то затрат фактически нет. Поэтому
хорошая мысль состоит в том, чтобы создавать подпрограммы удобного в
обращении размера и использовать их в больших количествах. Имя
подпрограммы позволяет добиваться значительной абстракции. Если
имена выбраны правильно, то у вас зачастую можете исчезнуть нужда в
комментариях.
Вообще, мне нравится, когда рабочая часть подпрограммы (ее текст, в
меньшей степени комментарии заголовка и так далее) видна полностью
при редактировании; она должна целиком помещаться на экране или в
окне.

37. Нужно обеспечивать возможность распечатки
всего текста программы
Часто проще найти ошибку на распечатанной странице, чем на экране,
даже на большом. Текст на бумаге легче читается вследствие более
высокого разрешения и лучшего фокусного расстояния, чем на экране.
При этом вы можете разложить бумажные распечатки по всей
поверхности стола. Вы вряд ли чем-нибудь сможете заменить бумагу в
этом плане, если только у вас нет монитора с диагональю полтора метра и
разрешением 300 точек на дюйм.

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


Формат ирование и документ ация 49
шрифта), даже если ваш текстовый редактор поддерживает скроллинг по
горизонтали. Если вы выйдете за эти пределы, то будете вынуждены
использовать столь мелкий шрифт для вывода, что совсем не сможете
прочитать свою распечатку.

38. Используйте штриховую линию для зрительного
разделения подпрограмм
Я всегда ставлю такой комментарий:

//---------------------------------------------------------

над каждым определением функции. (И к тому же, я не использую
штриховую линию нигде более). Хотя пустые строки замечательно
подходят для зрительного разделения блоков программы, исключительно
их использование не дает эффекта. Штриховая линия между функциями
облегчает их поиск. Так же как пустая строка указывает на границу
абзаца, штриховые линии подобны заголовкам разделов. Если мне нужно
еще более четкое разделение, то я использую:

//==========================================================
//
ОПИСЫВАЮЩИЙ ТЕКСТ
//==========================================================

Подумайте об этом, как о заголовке для главы. Я стараюсь не помещать
никаких комментариев, за исключением штриховых линий, вне функции,
потому что такие комментарии затрудняют обнаружение определений
этой функции. При этом я форматирую функции следующим образом:
//---------------------------------------------------------
void descriptive_name( type descriptive_name )
{
// Если имена функции и аргументов недостаточно содержа//
тельны, то я помещаю здесь комментарий, описывающий,
// что она делает. Я опускаю этот комментарий, если имена
// достаточно понятны. (Соответствующее правило гласит:
// "
Не объясняй очевидного").
//
// Затем я описываю возвращаемое значение и аргумент.
// И вновь вы можете не использовать комментарий, если
// имена достаточно удачные.
//
// Наконец, я помещаю здесь комментарий, описывающий, как
// функция делает то, что она делает. И снова я пропускаю
// этот комментарий, если программа сама по себе достаточно
// содержательна.

code_goes_here();
}


Правила программирования на Си и Си++

50


39. Пробел - один из наиболее эффективных
комментариев
Это кажется мелочью, но это может чрезвычайно улучшить читаемость
вашей программы. Обратите внимание, как используются пробелы в этой
книге в качестве организующего средства, и вы поймете, как
использовать их в своей собственной программе. Пустые строки (или
отступ в первой строке) зрительно разделяют абзацы. Пробел следует за
точкой, но не должен ей предшествовать, потому что точка завершает
что-либо. Идея вам ясна. А вот правила:
+ Разбивайте текст программы на логические куски (т.е. абзацы), где
каждый кусок выполняет одну операцию. Окружите эти куски или
пустыми строками, или строками с фигурными скобками.
+ За знаком препинания всегда должен идти пробел.
+ Операторы являются сокращениями слов. Когда вы видите "+", то
говорите "плюс". Подобно любому сокращению, вы должны окружать
идентификатор пробелами. (Например: a + b читается "a плюс b",
a+b
читается "aплюсb").

+ Исключение составляют унарные операторы, которые
рассматриваются как словарные префиксы или суффиксы (*p, a--,
f(arg,arg)
и т.д.).
+ . или -" в Си/Си++ являются эквивалентом символа подчеркивания.
До и после них пробелов быть не должно: p-"msg(), obj.msg().

Вот пример того, что может произойти, когда вы что-нибудь
упаковываете слишком плотно. Рассмотрим:

int *p;
y=(x/*p++);
f(int /* вставка */);

Если вы удалите комментарии, то получите:

int *p;
y=(x
);

Сочетание /* в выражении y=(x/*p++) расценивается как символ
начала комментария, который заканчивается сочетанием */ в вызове
функции f(). (Такой случай действительно со мной произошел, и мне
потребовался целый день, чтобы в нем разобраться. Естественно,
компилятор не давал сообщений об ошибках, потому что здесь все
синтаксически правильно).


Формат ирование и документ ация 51
Еще замечание по данному поводу. Мне часто приходилось позднее
видеть объявления подобные следующему:

int
* x;

Проблема состоит в том, что:

int
* x, y;

не объявляет два указателя, как подсказывает распределение пробелов.
Здесь мы имеем на самом деле еще одну проблему из рода "я могу на
любом языке программирования писать как на ФОРТРАНЕ". Было бы
прекрасно, если бы Си работал так же, как подсказывает предыдущее
форматирование, но это не так. После правильного форматирования

int *x, y;

становится совершенно ясно, что x - указатель, а y - нет.

40. Используйте отступы в четыре пробела
Никлас Вирт, который изобрел языки Паскаль и Модула-2, однажды
выпустил книгу, где всюду использовались отступы в один символ.
Чтение в ней листингов стало одним из самых тяжелых случаев в моей
практике. Используйте достаточно большие отступы, чтобы ваш читатель
мог сказать, что в тексте видно абзацы; четыре пробела кажутся
идеальными.
Вы должны делать отступы последовательно. Даже во внешнем блоке
подпрограммы должны быть отступы. Такой вариант неприемлем:

void f( void )
{
if( x )
yyy();
more_code();
even_more_code();
}

потому что слишком трудно найти начало подпрограммы. Сравните
предыдущий вариант со следующим:

void f( void )
{
if
( x )
yyy();
more_code();
even_more_code();
}


Правила программирования на Си и Си++

52


41. Условные операторы выделяются абзацными
отступами
Я делаю это даже в операторах из одной строки:

if
( by_land )
one();
else
two();

а не так:

if
( by_land ) one() else two();

Очевидным исключением является:

if
( by_land )
{
one();
}
else if
( by_sea )
{
two();
}
else if
( by_air )
{
three();
}

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

if
( by_land )
one(); if
(debug) printf("
Ох...");
else if ( by_sea )

что привело привело фактически к:

if
( by_land )
one();

if (debug)
printf("
Ох...");
else
if
( by_sea )

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


Формат ирование и документ ация 53

if ( by_land ) one();
else if ( by_sea ) two();
else if ( by_tunnel ) three();

но это - нечитаемо:

if (by_land)one();
else if
(by_sea)two();
else if
(by_tunnel)three();

Подобный код никуда не годится:

for ( a ; b ; c );
while ( ++i " 10 );

Слишком просто случайно сделать следующее:

while ( i " 10 );
++i;

(Другими словами, "вечно сравнивать i с 10, затем увеличить i"). Если
точка с запятой никогда не ставится в конце строки, начинающейся с for
или while, то вы можете использовать утилиту поиска строки типа grep
для обнаружения таких проблем.

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

f()
{
/* Здесь идет
** длинный комментарий
*/
code();
}

Здесь имеется сходная проблема:

f()
{
int local_var;
int another_local_var;
code();
}

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


Правила программирования на Си и Си++

54


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

f()
{
int local_var;
int another_local_var;
code();
}

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

while ( some_condition )
{
// внутренний блок
}

Я в самом деле не люблю так называемый стиль Кэрнигана и Ричи:

if
( condition ){
code();
}else{
more_code();
}

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

43. Используйте скобки, если в условном операторе
имеется более, чем одна строка
Это правило применяется, если даже дополнительными строками
является комментарий. Проблема заключается в том, что слишком легко
случайно добавить другой оператор и забыть добавить скобки. Текст,
подобный нижеследующему, более надежен:

if
( something() )
{

*/ Quo usque tandem abutere Gatesalina, patientia nostra.

"До каких же пор ты, Катилина, будешь испытывать наше терпение..." - начало
известной речи Цицерона. - Прим. перев.


Формат ирование и документ ация 55

*/
somethig_else();
}



Часть

Имена и идентификаторы

Имена играют важную роль. Правильно выбранные имена могут сделать
программу поистине самодокументированной, не требуя совсем или
требуя мало дополнительного набора в виде явных комментариев. Плохо
выбранные имена (например, state - состояние или штат ?) могут
добавить ненужную сложность в вашу программу. Эта часть книги
содержит правила выбора имен.


Формат ирование и документ ация 57

44. Имена должны быть обычными словами
английского языка, описывающими то, что
делает функция, аргумент или переменная
Избегайте аббревиатур; они ухудшают читабельность программ.
Некоторые по привычке продолжают использовать аббревиатуры, что
приводит к абсурдной практике, типа отбрасывания последней буквы
слова или удаления из него всех гласных. Возьмем странно названную
функцию UNIX creat(); очевидно, что create() лучше. Я также видел
странности типа lnghth вместо length и mt вместо empty.
При этом общепринятые аббревиатуры являются очевидными
исключениями. Вот тот минимум из них, которым я пользуюсь сам:

col - индекс столбца;
cur - текущий;
i j - обобщенные счетчики циклов;
max - максимум (обычно в качестве префикса или суффикса);
min - минимум (обычно в качестве префикса или суффикса);
obj - обобщенный объект (имеется указатель на базовый класс,
но производный класс не известен);
p ptr - обобщенный указатель;
s str - строка (в языке Си обычно char*),

но не употребляйте их, если называемый объект используется не как
обобщенный. Например, i имеет смысл использовать в качестве счетчика
цикла в операторе for, если этот счетчик используется просто для
подсчета числа итераций:

for
( i = 10; --i "= 0; ) // нарисовать 10 тире
putchar('-');

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

for
( i = 0; i " imax; ++i )
for
( j = 0; j " jmax; ++j )
move_cursor( i,j );

со следующим:

for
( row = 0; row " max_row; ++row )
for
( col = 0; col " max_col; ++col )
move_cursor( row, col );

Я также избегаю использовать x и y вместо row и column. Одно из ранее
указанных правил рекомендует пропускать программу через систему
проверки орфографии. Действительное достоинство этого метода состоит


Правила программирования на Си и Си++

58


в том, что он побуждает вас использовать в качестве имен обычные слова.

44.1. Не используйте в качестве имен тарабарщину
Отличный образец такого подхода можно наблюдать в любом
предлагаемом Microsoft примере программы, хотя эта проблема ни в коем
случае не ограничивается корпорацией Microsoft. Все демонстрационные
программы Microsoft Windows включают тип переменной в ее имя.
Например, объявление типа:

const char *str;

будет сделано следующим образом:

LPCSTR lpszstr;

Переведите lpszstr как "указатель типа long с именем str на строку,
оканчивающуюся 0". На самом деле здесь несколько проблем, не
последней из которых является тот факт, что LPCSTR скрывает наше
объявление указателя. Тем не менее, обсуждаемое правило посвящается
проблеме самого имени.
Этот стиль выбора имен называется "венгерской" записью по названию
родины руководителя отдела программирования Microsoft Чарльза
Саймони, который его изобрел. (а не потому, что его использование
придает программам Microsoft такой вид, как будто они написаны на
венгерском языке.)
Венгерская запись целесообразна для языка ассемблера, в котором все,
что вы знаете о переменной - это ее размер. Включение информации о
типе в имя переменной позволяет вам контролировать правильность ее
использования.2 Языки более высокого уровня типа Си и Си++
используют для этой цели объявление переменных.
Доктор Саймони несколько раз в печати защищал такой метод записи,
но я бы не стал его рекомендовать для программ на Си или Си++. По
моему мнению, венгерская запись не дает ничего, кроме ухудшения
читаемости программ. Простые str или string значительно легче
читаются и содержат ту же информацию. Если вам на самом деле нужно
узнать тип, то для этого достаточно вернуться к определению.3

2 Я подозреваю, что венгерская запись так интенсивно используется вследствие
того, что большая часть Microsoft Windows написана на языке ассемблера.
3 По крайней мере, оно должно быть. Я подозреваю, что некоторые энтузиасты
венгерской записи так плохо организуют свои программы, что просто не могут найти
нужные объявления. Включая тип в имя, они избавляются от многих часов поисков в
неудачно спроектированных листингах. Программы на языке ассемблера, которые по


Формат ирование и документ ация 59

Существует и более распространенный, хотя и менее радикальный прием,
при котором имена указателей начинают символом p. Эта практика тоже
загромождает программу. Вы ведь не начинаете имена целочисленных
переменных типа int символом i, переменных типа double - d, а
функций - f? Очевидным исключением является случай, когда у вас есть
объект и указатель на этот объект в одной и той же области видимости:

char str[128], *pstr = str;

c другой стороны, для указателя, вероятно, лучше содержательное имя.
Сравните:

char str[128], *first_nonwhite = str;
while ( isspace(*first_nonwhite) )
++first_nonwhite;
// В этой ситуации имя *first_nonwhite говорит вам гораздо
// больше о том, что делает переменная, чем предыдущее "*pstr".

45. Имена макросов должны записываться
ЗАГЛАВНЫМИ_БУКВАМИ
Как показывается в последующих разделах, макросы часто вызывают
побочные эффекты. Поэтому полезно иметь возможность определить с
первого взгляда, что у вас является макросом. Конечно, вы не должны
использовать только заглавные буквы для чего-нибудь помимо макросов,
иначе вы не достигнете цели данного правила.

45.1. Не используйте заглавных букв для констант
перечисления
Должна быть обеспечена возможность замены констант, определенных в
перечислении, на переменную типа const. Если ее имя записано
заглавными буквами, то вам придется его менять. Кроме того, имеются
еще и проблемы с макросами (вскоре будут рассмотрены), которых нет у
перечислений. Поэтому будет полезно иметь возможность различать их с
первого взгляда.

45.2. Не используйте заглавных букв в именах
типов, созданных при помощи typedef
Так как макрос также может использоваться в манере, подобной
typedef
, то полезно знать может или нет что-то быть использовано в
качестве синтаксически правильного типа. Например, имея:

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


Правила программирования на Си и Си++

60


typedef void (*ptr_to_funct)(int );

вы можете написать следующее:
(ptr_to_funct)( p ); // преобразует p в указатель на функцию
ptr_to_funct f(long); // f возвращает указатель на функцию

Макрос типа:

#define PTR_TO_FUNCTION void (*) (int )

позволяет вам сделать преобразование:

(PTR_TO_FUNCTION) ( p );

но не позволяет объявить функцию:

PTR_TO_FUNCTION f(long);

Указанный макрос при подстановке дает:

void (*) (int ) f(long);

но компилятору нужно:

void (*f(long))(int );

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

46. Не пользуйтесь именами из стандарта ANSI Cи
Идентификаторы, начинающиеся с символа подчеркивания, и имена
типов, оканчивающиеся на _t, были зарезервированы стандартом ANSI
Cи для использования разработчиками компиляторов. Не используйте эти
символы. Также избегайте имен функций, вошедших в стандарт ANSI Cи
и в проект стандарта ISO/ANSI для Си++.


47. Не пользуйтесь именами Microsoft
Это может показаться правилом, специфичным только для Microsoft, но
на самом деле это не так (учитывая имеющуюся склонность Microsoft к
мировому господству). Любой, кто заботится о переносимости, должен
рассчитывать на то, что его или ее программа со временем может или
работать под управлением операционной системы Microsoft, или
взаимодействовать с библиотекой классов Microsoft. Библиотека MFC,

В августе 1998 г. стандарт ратифицирован в виде "ISO/IEC 14882, Standard for
the C++ Programming Language". Популярно изложен в книге: Страуструп Б. Язык
программирования С++, 3-е изд. /Пер. с англ.-СПб.; М.: "Невский диалект" -
"Издательство БИНОМ", 1999.-991 с. - Прим. перев.


Формат ирование и документ ация 61

например, перенесена на Macintosh и во многие операционные среды
UNIX/Motif на момент написания этой книги, и, вероятно, появится на
других операционных системах в ближайшем будущем.
На момент написания этой книги интерфейс прикладного
программирования Windows (API) включает в себя около 1200 функций.
Библиотека MFC, быстро вытесняющая первоначальный интерфейс на
языке Си, добавляет около 80 определений классов. К сожалению, метод
Microsoft состоит в добавлении каждый раз дополнительных функций и
классов в новую версию компилятора. Если Microsoft случайно выберет в
качестве имени для функции или класса то, которое вы используете для
каких-то других целей, угадайте, кому из вас придется его сменить?
Так как ни один из идентификаторов Microsoft не соответствует
стандарту ANSI Cи, требующему, чтобы имена поставляемых
разработчиком объектов начинались с символа подчеркивания, то вы
должны предохраняться, избегая использования соглашений по выбору
имен в стиле Microsoft:
+ Все имена функций Microsoft используют соглашения в стиле Паскаля
о СмесиЗаглавныхИСтрочныхБукв(), и они всегда начинаются с
заглавной буквы. Я предпочитаю имена только из строчных букв с
символами подчеркивания, но что бы вы ни выбрали,
НеИспользуйтеСтильMicrosoft(). Функции-члены в классах MFC
используют то же самое соглашение.
+ Все имена классов Microsoft начинаются с заглавной "С" с
последующей заглавной буквой (например, CString, CWnd, CDialog
и т.д.). Начальная "С" мало что дает, кроме беспорядка, и ее пропуск
удаляет нас от области имен Microsoft.
+ Одна из наиболее фундаментальных заповедей объектноориентированного
проектирования запрещает оставлять
незащищенными данные-члены в определении класса. Тем не менее,
многие классы MFC имеют открытые поля данных. Все эти поля
начинаются с m_, не имеющих другого назначения, кроме как
увеличить беспорядок. Тем не менее, мы можем использовать эту
бессмыслицу для того, чтобы не начинать имена своих собственных
полей с m_ и таким образом легко отличать свои члены от
унаследованных из базовых классов MFC.


Правила программирования на Си и Си++

62


48. Избегайте ненужных идентификаторов
Имена для констант часто вообще не нужны. Например, не определяйте
значения, возвращаемые при ошибке; если возвращается всего одна
ошибка, возвратите просто FALSE. Не делайте так:

enum { INSERT_ERROR, DELETE_ERROR };

insert()
{
//...
return INSERT_ERROR;
}

delete()
{
//...
return DELETE_ERROR;
}

а просто возвратите 0 в случае ошибки и в случае успеха любое
правильное значение типа 1.

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

int nwords(const char *str)
{
typedef enum { IN_WORD, BETWEEN_WORDS } wstate;

int word_count = 0;
wstate state = BETWEEN_WORDS;
for
(; *str ; ++str )
{
if
( isspace(*str) )
state = BETWEEN_WORDS;
else
if
( state != IN_WORD )
{
++word_count;
state = IN_WORD;
}
}
return word_count;
}

Неправильно выбранное

Список сторінок

Поділитися у соцмережах

☏ Замовлення реклами: +3805044-68-872

© Асоціація електронних бібліотек України

☝ Усі матеріали сайту (зокрема статті, зображення, рекламні оголошення тощо) призначені лише для попереднього ознайомлення. Усі права на публікації, представлені на сайті, належать їхнім законним власникам. Просимо Вас не зберігати копії інформації.