Главная страница

Совершенный код. Совершенный код. Мастер-класс. Стив Макконнелл. Руководство по стилю программирования и конструированию по


Скачать 5.88 Mb.
НазваниеРуководство по стилю программирования и конструированию по
АнкорСовершенный код
Дата31.03.2023
Размер5.88 Mb.
Формат файлаpdf
Имя файлаСовершенный код. Мастер-класс. Стив Макконнелл.pdf
ТипРуководство
#1028502
страница89 из 106
1   ...   85   86   87   88   89   90   91   92   ...   106
ГЛАВА 31 Форматирование и стиль
729
Избегайте отсутствия отступов в парах begin'end В стиле форматиро#
вания, проиллюстрированном в листинге 31#24, пара
begin%end выровнена по гра#
нице управляющей структуры, а в выражениях, охватываемых операторами
begin
и
end, сделаны отступы относительно begin.
Листинг 31-24. Пример пары begin-end, не выделенной отступами (Java)
Ключевое слово begin выровнено по границе for.
for ( int i = 0; i < MAX_LINES; i++ )
{
В выражениях сделан отступ относительно begin.
ReadLine( i );
ProcessLine( i );
Слово end также выровнено по границе структуры for.
}
Хотя такой подход выглядит хорошо, он нарушает Основную теорему формати#
рования, так как не показывает логическую структуру кода. При таком располо#
жении
begin и end не являются частью управляющей структуры, но в то же время,
они не являются и частью блока выражений, расположенного далее.
Листинг 31#25 демонстрирует абстрактное представление этого подхода:
Листинг 31-25. Абстрактный пример вводящего в заблуждение выравнивания
A XXXXXXXXXXXXXXXXXXXX
B XXXXXXX
C XXXXXXXX
D XXXXXXXXXXXXXX
E XXXX
Можно ли сказать, что оператор B подчиняется оператору A? Он не выглядит ча#
стью оператора A, и нет оснований считать, что он ему подчиняется. Если вы используете такой подход, смените его на один из двух вариантов стиля, описан#
ных ранее, и ваше форматирование будет более целостным.
Избегайте двойных отступов при использовании begin и end Следствием правила относительно использования пары
begin%end без отступов является слу#
чай, касающийся дополнительных отступов после
begin%end. Этот стиль, продемон#
стрированный в листинге 31#26, содержит отступы как перед
begin и end, так и перед выражениями, которые они охватывают:
Листинг 31-26. Пример неуместного двойного отступа
в блоке begin-end (Java)
for ( int i = 0; i < MAX_LINES; i++ )
{
>
>
>

730
ЧАСТЬ VII Мастерство программирования
В выражениях после begin сделан лишний отступ.
ReadLine( i );
ProcessLine( i );
}
Еще один пример стиля, внешне привлекательного, но нарушающего Основную теорему форматирования. Исследования показали, что с точки зрения понимания программы, использующие одинарные и двойные отступы, не отличаются друг от друга (Miaria et al., 1983), но приведенный стиль неточно отображает логическую структуру программы.
ReadLine() и ProcessLine() показаны так, будто они подчи#
нены паре
begin%end, что не соответствует действительности.
Этот подход также преувеличивает сложность логической структуры программы.
Какая из структур, приведенных в листингах 31#27 и 31#28, выглядит сложнее?
Листинг 31-27. Абстрактная структура 1
XXXXXXXXXXXXXXXXXXXX
XXXXX
XXXXXXXXX
XXXXXXXXXXXX
XXXXX
Листинг 31-28. Абстрактная структура 2
XXXXXXXXXXXXXXXXXXXX
XXXXX
XXXXXXXXXX
XXXXXXXXXXXXX
XXXXX
Обе являются абстрактными представлениями структуры цикла
for. Структура 1
выглядит сложней, хотя и представляет тот же код, что и Структура 2. Если бы вам понадобилось создать 2 или 3 уровня вложенности операторов, то из#за двойных отступов вы получили бы 4 или 6 уровней отступов. Такое форматирование вы#
глядело бы сложнее, чем на самом деле. Избавьтесь от этой проблемы, эмулиро#
вав явные блоки или применив
begin и end в качестве границ блока, выравнивая
begin и end так же, как и выражения, которые они охватывают.
Другие соглашения
Хотя отступы в блоках являются главным вопросом форматирования управляю#
щих структур, вы также можете столкнуться с другими проблемами, поэтому да#
лее я привожу еще несколько советов.
Используйте пустые строки между абзацами Некоторые блоки не разгра#
ничиваются с помощью пар
begin%end. Логический блок — группа подходящих друг к другу операторов — должны рассматриваться так же, как абзацы в обычной книге.
Отделяйте их друг от друга с помощью пустых строк. Листинг 31#29 представля#
ет пример абзацев, которые следует разделить:
>

ГЛАВА 31 Форматирование и стиль
731
Листинг 31-29. Пример кода, который следует сгруппировать
и разбить на абзацы (C++)
cursor.start = startingScanLine;
cursor.end = endingScanLine;
window.title = editWindow.title;
window.dimensions = editWindow.dimensions;
window.foregroundColor = userPreferences.foregroundColor;
cursor.blinkRate = editMode.blinkRate;
window.backgroundColor = userPreferences.backgroundColor;
SaveCursor( cursor );
SetCursor( cursor );
Этот код выглядит неплохо, но пустые строки позволят улуч#
шить его с двух точек зрения. Во#первых, если у вас есть груп#
па операторов, не требующих выполнения в определенном порядке, заманчиво объединить их именно так. Вам не надо усовершенствовать порядок выражений для помощи компь#
ютеру, но читатели#люди оценят дополнительные подсказ#
ки о том, какие операторы выполнять в определенном порядке, а какие — просто расположены рядом друг с другом. Дисциплина добавления пустых строк в коде программы заставляет вас тщательней обдумывать вопрос, какие операторы на самом деле подходят друг другу. Исправленный фрагмент кода, представленный в листинге
31#30, показывает, как организовать данный набор операторов.
Листинг 31-30. Пример кода, который правильно сгруппирован
и разбит на абзацы (C++)
Эти строки настраивают текстовое окно.
window.dimensions = editWindow.dimensions;
window.title = editWindow.title;
window.backgroundColor = userPreferences.backgroundColor;
window.foregroundColor = userPreferences.foregroundColor;
Эти строки устанавливают параметры курсора и должны быть отделены от предыдущих строк.
cursor.start = startingScanLine;
cursor.end = endingScanLine;
cursor.blinkRate = editMode.blinkRate;
SaveCursor( cursor );
SetCursor( cursor );
Реорганизованный код подчеркивает выполнение двух разных действий. В пер#
вом примере недостатки организации операторов, отсутствие пустых строк, а также старый трюк с выравниванием знаков равенства приводили к тому, что выраже#
ния выглядели связанными сильнее, чем на самом деле.
Второе преимущество использования пустых строк, приводящее к улучшению кода,
заключается в появлении естественного промежутка для добавления комментариев.
В листинге 31#30 комментарии над каждым блоком станут отличным дополнени#
ем к улучшенному формату.
>
>
Перекрестная ссылка Если вы придерживаетесь Процесса про- граммирования с псевдокодом,
ваш код автоматически разби- вается на абзацы (см. главу 9).

732
ЧАСТЬ VII Мастерство программирования
Форматируйте блоки из одного оператора единообразно Блоком из од#
ного оператора называется единственный оператор, следующий за управляющей структурой, например, оператор, который следует за проверкой условия
if. В этом случае для корректной компиляции пара
begin и end необязательна, и вы можете выбрать один из трех вариантов стиля, показанных в листинге 31#31:
Листинг 31-31. Пример вариантов стиля для блоков из одного оператора (Java)
Стиль 1
if ( expression )
одиноператор;
Стиль 2а if ( expression ) {
одиноператор;
}
Стиль 2б if ( expression )
{
одиноператор;
}
Стиль 3
if ( expression ) одиноператор;
Существуют аргументы в защиту каждого из этих трех подходов. Стиль 1 следует схеме отступов, используемой для блоков, поэтому он согласуется с остальными подходами. Стиль 2 (как 2а, так и 2б) также не противоречит общим правилам, а пары
begin%end уменьшают вероятность добавления операторов после условия if,
не указав
begin и end. Это станет особенно трудноуловимой ошибкой, потому что отступы будут подсказывать вам, что все в порядке, однако компилятор не интер#
претирует отступы. Основное преимущества Стиля 3 над Стилем 2 в том, что его легче набирать. Его преимущество над стилем 1 в том, что при копировании в другую часть программы он с большей вероятностью будет скопирован правиль#
но. А недостаток в том, что при использовании построчного отладчика такая строка будет рассматриваться как одно целое и вы не узнаете, выполняется ли выраже#
ние после проверки условия
if.
Я использовал Стиль 1 и много раз становился жертвой неправильной модифи#
кации. Мне не нравится нарушение стратегии отступов в Стиле 3, поэтому его я тоже избегаю. В групповых проектах я предпочитаю вариации Стиля 2 из#за по#
лучаемого единообразия и возможности безопасной модификации. Независимо от избранного стиля применяйте его последовательно и используйте один и тот же стиль в условиях
if и во всех циклах.
В сложных выражениях размещайте каждое условие на отдельной строке
Размещайте каждую часть сложного выражения на отдельной строке. Листинг 31#32
содержит пример выражения, форматированного без учета удобочитаемости:
>
>
>
>

ГЛАВА 31 Форматирование и стиль
733
Листинг 31-32. Пример совершенно неформатированного
(и нечитаемого) сложного выражения (Java)
if (((‘0’ <= inChar) && (inChar <= ‘9’)) || ((‘a’ <= inChar) &&
(inChar <= ‘z’)) || ((‘A’ <= inChar) && (inChar <= ‘Z’)))
Это пример форматирования для машины, а не для человека. Разбив выражение на несколько строк, как показано в листинге 31#33, вы можете обеспечить более удобное чтение.
Листинг 31-33. Пример вполне читаемого сложного выражения (Java)
if ( ( ( ‘0’ <= inChar ) && ( inChar <= ‘9’ ) ) ||
( ( ‘a’ <= inChar ) && ( inChar <= ‘z’ ) ) ||
( ( ‘A’ <= inChar ) && ( inChar <= ‘Z’ ) ) )
Второй фрагмент использует несколько методов формати#
рования (отступы, пробелы, размещение значений в соот#
ветствии с числовой прямой и выделение незавершенных строк) — в результате выражение вполне можно прочесть.
Более того, назначение проверки понятно. Если выражение содержит небольшую ошибку, скажем, указана
z вместо Z, то при таком форматировании в отличие от менее аккуратного варианта это сразу бросится в глаза.
Избегайте операторов goto Первоначальная причина отказа от
goto состояла в том, что они затрудняли доказа#
тельство корректности программы. Это хороший аргумент для тех, кто хочет доказать, что их программы корректны, т. е. практически ни для кого. Для большинства программистов более насущная проблема состоит в том,
что операторы
goto затрудняют форматирование кода. Делаете ли вы отступ в коде между
goto и меткой, на которую он переходит? Что, если у вас есть несколько goto,
использующих одну и ту же метку? Размещаете ли вы их один под другим? Вот несколько советов по форматированию
goto.

Избегайте
goto. Это также позволит обойти проблемы форматирования.

Для меток перехода используйте только заглавные бук#
вы. Это делает метки очевидными.

Помещайте оператор, содержащий
goto, на отдельной строке. Это делает использование
goto очевидным.

Помещайте метку перехода
goto на отдельной строке.
Окружите ее пустыми строками. Это подчеркнет наличие метки. Строку, содер#
жащую метку, выравнивайте по левому краю, чтобы сделать метку настолько очевидной, насколько это возможно.
Листинг 31#34 показывает эти соглашения по форматиро#
ванию
goto в действии.
Перекрестная ссылка Другая методика повышения удобочи- таемости сложных выражений
— перенести их в логические функции (см. раздел 19.1).
Перекрестная ссылка Об опера- торе goto см. раздел 17.3.
Метки goto должны быть выров- нены влево, состоять из заглав- ных букв и содержать имя про- граммиста, его домашний теле- фон и номер кредитной карты.
Абдул Низар (Abdul Nizar)
Перекрестная ссылка О других способах решения этой пробле- мы см. подраздел «Обработка ошибок и операторы goto» раз- дела 17.3.

734
ЧАСТЬ VII Мастерство программирования
Листинг 31-34. Пример наилучшего выхода
из плохой ситуации (использование goto) C++
void PurgeFiles( ErrorCode & errorCode ) {
FileList fileList;
int numFilesToPurge = 0;
MakePurgeFileList( fileList, numFilesToPurge );
errorCode = FileError_Success;
int fileIndex = 0;
while ( fileIndex < numFilesToPurge ) {
DataFile fileToPurge;
if ( !FindFile( fileList[ fileIndex ], fileToPurge ) ) {
errorCode = FileError_NotFound;
Здесь используется goto.
goto END_PROC;
}
if ( !OpenFile( fileToPurge ) ) {
errorCode = FileError_NotOpen;
Здесь используется goto.
goto END_PROC;
}
if ( !OverwriteFile( fileToPurge ) ) {
errorCode = FileError_CantOverwrite;
Здесь используется goto.
goto END_PROC;
}
if ( !Erase( fileToPurge ) ) {
errorCode = FileError_CantErase;
Здесь используется goto.
goto END_PROC;
}
fileIndex++;
}
Здесь находится метка goto. Заглавные буквы и специальное форматирование служат для того,
чтобы метку было сложно не заметить.
END_PROC:
DeletePurgeFileList( fileList, numFilesToPurge );
}
>
>
>
>
>

ГЛАВА 31 Форматирование и стиль
735
Пример кода на C++, приведенный в листинге 31#34 достаточно длинный, и вы можете представить себе обстоятельства, когда опытный программист может доб#
росовестно решить, что применение
goto будет наилучшим решением. В таком случае приведенный пример форматирования — лучшее, что вы можете сделать.
Не используйте форматирование в конце строки в
виде исключения для операторов case Одна из опас#
ностей форматирования в конце строки проявляется при ра#
боте с операторами
case. Популярный стиль форматирования case состоит в их выравнивании справа от описания каждого варианта (см. листинг 31#35). Проблема в том, что такой стиль приносит при сопровождении лишь головную боль.
Листинг 31-35. Пример сложного в сопровождении форматирования
в конце строки в операторе case (C++)
switch ( ballColor ) {
case BallColor_Blue: Rollout();
break;
case BallColor_Orange: SpinOnFinger();
break;
case BallColor_FluorescentGreen: Spike();
break;
case BallColor_White: KnockCoverOff();
break;
case BallColor_WhiteAndBlue: if ( mainColor == BallColor_White ) {
KnockCoverOff();
}
else if ( mainColor == BallColor_Blue ) {
RollOut();
}
break;
default: FatalError( “Unrecognized kind of ball.” );
break;
}
Если вы добавите вариант с более длинным, чем существующие, именем, вам при#
дется сдвигать все варианты и код, к ним относящийся. Изначально большие от#
ступы затрудняют размещение какой#то дополнительной логики, как показано в варианте
WhiteAndBlue. Решением этой проблемы будет переход к стандартному виду отступов. Так, если в циклах вы делаете отступ величиной в три пробела, исполь#
зуйте в вариантах
case такое же число пробелов, как показано в листинге 31#36:
Листинг 31-36. Пример хороших стандартных отступов в операторе case (C++)
switch ( ballColor ) {
case BallColor_Blue:
Rollout();
break;
case BallColor_Orange:
SpinOnFinger();
break;
Перекрестная ссылка Об опера- торах case см. раздел 15.2.

736
ЧАСТЬ VII Мастерство программирования case BallColor_FluorescentGreen:
Spike();
break;
case BallColor_White:
KnockCoverOff();
break;
case BallColor_WhiteAndBlue:
if ( mainColor = BallColor_White ) {
KnockCoverOff();
}
else if ( mainColor = BallColor_Blue ) {
RollOut();
}
break;
default:
FatalError( “Unrecognized kind of ball.” );
break;
}
Это пример ситуации, в которой многие могут предпочесть внешний вид перво#
го варианта. Однако с точки зрения размещения длинных строк, единообразия и удобства сопровождения второй подход имеет гораздо больше преимуществ.
Если у вас есть оператор
case, все варианты которого выглядят абсолютно одина#
ково, а все действия называются кратко, можно рассмотреть возможность разме#
щения и варианта, и действия на одной и той же строке. Однако в большинстве случаев вы впоследствии пожалеете об этом. Это форматирование изначально неудобно и портится при модификации. Кроме того, тяжело поддерживать оди#
наковую структуру всех вариантов в случае удлинения кратких имен действий.
31.5. Форматирование отдельных операторов
Ниже описаны способы улучшения отдельных выражений в программе.
Длина выражения
Общее и в какой#то мере устаревшее правило гласит, что длина строки выражения не должна превышать 80 симво#
лов, так как строки длиннее 80 символов:

тяжело читать;

препятствуют созданию глубокой вложенности;

часто не помещаются на стандартный лист бумаги, осо#
бенно если печатается по 2 страницы кода на каждой физической странице распечатки.
С появлением больших экранов, узких шрифтов и альбомной ориентации бума#
ги 80#символьное ограничение выглядит необоснованным. Одна строка в 90 сим#
волов часто удобнее для чтения, чем другая, разбитая на две только с целью избе#
жать выхода за 80#символьную границу. При современном уровне технологий,
Перекрестная ссылка О доку- ментировании отдельных выра- жений см. подраздел «Коммен- тирование отдельных строк»
раздела 32.5.

ГЛАВА 31 Форматирование и стиль
737
вероятно, будет правильным изредка допускать превышение ограничения в 80
колонок.
Использование пробелов для ясности
Добавляйте в выражение пробелы в целях повышения удобства чтения:
Используйте пробелы, чтобы сделать читаемыми логические выражения
Выражение:
while(pathName[startPath+position]<>’;’) and
((startPath+position)Как правило, следует отделять идентификаторы друг от друга с помощью пробе#
лов. Следуя этой рекомендации, выражение
while можно переписать так:
while ( pathName[ startPath+position ] <> ‘;’ ) and
(( startPath + position ) < length( pathName )) do
Некоторые художники от программирования могут предложить дальнейшее улуч#
шение данного выражения с помощью дополнительных пробелов для подчерки#
вания его логической структуры:
while ( pathName[ startPath + position ] <> ‘;’ ) and
( ( startPath + position ) < length( pathName ) ) do
Это хорошо, но и предыдущий вариант неплохо улучшал читабельность. Однако дополнительные пробелы вряд ли повредят, так что будьте на них щедрее.
Используйте пробелы, чтобы сделать читаемыми обращения к массиву
Выражение:
grossRate[census[groupId].gender,census[groupId].ageGroup]
не легче читать, чем приводимое ранее сжатое выражение
while. Добавляйте про#
белы вокруг каждого индекса массива для упрощения их чтения. После примене#
ния этого правила выражение будет выглядеть так:
grossRate[ census[ groupId ].gender, census[ groupId ].ageGroup ]
Используйте пробелы, чтобы сделать читаемыми аргументы методов
Назовите четвертый аргумент следующего метода:
ReadEmployeeData(maxEmps,empData,inputFile,empCount,inputError);
А теперь назовите четвертый аргумент этого метода:
GetCensus( inputFile, empCount, empData, maxEmps, inputError );
Какой из них было легче найти? Вопрос вполне закономерный, поскольку поря#
док аргументов имеет большое значение во всех основных процедурных языках.
Довольно часто на одной половине экрана приходится располагать определение метода, на другой — его вызов и сравнивать каждый формальный параметр с со#
ответствующим фактическим параметром.

738
ЧАСТЬ VII Мастерство программирования
Форматирование строк с продолжением
Одна из наиболее неприятных проблем форматирования — решение о том, что делать с частью выражения, переносимой на следующую строку. Делать ли нор#
мальный отступ? Или выровнять его по ключевому слову? А что делать с присва#
иваниями?
Далее приведен здравый, последовательный подход, особенно полезный в Java, C,
C++, Visual Basic и других языках, поощряющих длинные имена переменных.
Сделайте так, чтобы незавершенность выражения была очевидна
Иногда выражение должно разбиваться на несколько строк либо потому, что оно длинней, чем это позволяют стандарты программирования, либо потому, что оно слишком длинное, чтобы поместиться в одной строке. Сделайте очевидным факт,
что часть выражения на первой строке является всего лишь частью. Самый про#
стой способ добиться этого — разбить выражение так, чтобы его часть на первой строке стала вопиюще некорректной, если рассматривать ее отдельно. В листин#
ге 31#37 приведены некоторые примеры:
Листинг 31-37. Примеры очевидно незавершенных выражений (Java)
Оператор && сигнализирует, что выражение не завершено.
while ( pathName[ startPath + position ] != ‘;’ ) &&
( ( startPath + position ) <= pathName.length() )
Знак плюс сигнализирует, что выражение не завершено.
totalBill = totalBill + customerPurchases[ customerID ] +
SalesTax( customerPurchases[ customerID ] );
Запятая сигнализирует, что выражение не завершено.
DrawLine( window.north, window.south, window.east, window.west,
currentWidth, currentAttribute );
Кроме сообщения о незавершенности выражения в первой строке, такое разбие#
ние предотвращает неправильную модификацию. Если удалить продолжение вы#
ражения, первая строка не будет выглядеть так, будто вы просто забыли скобку или точку с запятой — оно явно требует чего#то еще.
Альтернативный подход, также хорошо работающий, — размещение знака про#
должения в начале перенесенной строки, как показано в листинге 31#38.
Листинг 31-38. Примеры очевидно незавершенных выражений —
альтернативный стиль (Java)
while ( pathName[ startPath + position ] != ‘;’ )
&& ( ( startPath + position ) <= pathName.length() )
>
>
>

1   ...   85   86   87   88   89   90   91   92   ...   106


написать администратору сайта