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

Руководство по стилю программирования и конструированию по


Скачать 7.6 Mb.
НазваниеРуководство по стилю программирования и конструированию по
Дата18.05.2023
Размер7.6 Mb.
Формат файлаpdf
Имя файлаCode_Complete.pdf
ТипРуководство
#1139697
страница87 из 104
1   ...   83   84   85   86   87   88   89   90   ...   104
ГЛАВА 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() )
>
>
>

ГЛАВА 31 Форматирование и стиль
739
totalBill = totalBill + customerPurchases[ customerID ]
+ SalesTax( customerPurchases[ customerID ] );
Хотя такой стиль не приведет к появлению синтаксической ошибки при повиса- нии операторов
&& или +, он сильно упрощает поиск операторов по левой гра- нице столбца — там, где текст выровнен, а не по правой — где край текста неров- ный. Его дополнительное преимущество в том, что он позволяет пояснить струк- туру операций, как показано в листинге 31-39.
Листинг 31-39. Пример стиля, поясняющего смысл сложной операции (Java)
totalBill = totalBill
+ customerPurchases[ customerID ]
+ CitySalesTax( customerPurchases[ customerID ] )
+ StateSalesTax( customerPurchases[ customerID ] )
+ FootballStadiumTax()
- SalesTaxExemption( customerPurchases[ customerID ] );
Располагайте сильно связанные элементы вместе Разбивая строку на части,
оставляйте рядом взаимосвязанные элементы: обращения к массиву, аргументы ме- тода и т. д. Пример, приведенный в листинге 31-40, демонстрирует плохой вариант:
Листинг 31-40. Пример неправильного
разбиения строки (Java)
customerBill = PreviousBalance( paymentHistory[ customerID ] ) + LateCharge(
paymentHistory[ customerID ] );
Надо признать, что такой разрыв строки соответствует принципу подчеркивания очевидности незавершенных выражений, но это сделано так, что надобности зат- рудняет чтение выражения. Можно придумать ситуации, когда такое разбиение будет оправданным, но это не тот случай. Лучше оставить все обращения к мас- сиву на одной строке. Листинг 31-41 показывает лучшее форматирование:
Листинг 31-41. Пример правильного разбиения строки (Java)
customerBill = PreviousBalance( paymentHistory[ customerID ] ) +
LateCharge( paymentHistory[ customerID ] );
При переносе строк в вызове метода используйте отступ стандартно-
го размера Если в циклах и условных выражениях вы обычно делаете отступ в три пробела, то при переносе строки в вызове метода тоже сделайте отступ в три пробела. Листинг 31-42 содержит несколько примеров:
Листинг 31-42. Примеры переноса строк в вызовах методов
с применением стандартного размера отступов (Java)
DrawLine( window.north, window.south, window.east, window.west,
currentWidth, currentAttribute );
SetFontAttributes( faceName[ fontId ], size[ fontId ], bold[ fontId ],
italic[ fontId ], syntheticAttribute[ fontId ].underline,
syntheticAttribute[ fontId ].strikeout );

740
ЧАСТЬ VII Мастерство программирования
Одной из альтернатив такому подходу служит выравнивание перенесенных строк под первым аргументом, как показано в листинге 31-43:
Листинг 31-43. Пример отступов при переносе строк в вызове методов,
позволяющих выделить имена методов (Java)
DrawLine( window.north, window.south, window.east, window.west,
currentWidth, currentAttribute );
SetFontAttributes( faceName[ fontId ], size[ fontId ], bold[ fontId ],
italic[ fontId ], syntheticAttribute[ fontId ].underline,
syntheticAttribute[ fontId ].strikeout );
С эстетической точки зрения, в сравнении с первым вариантом этот код выгля- дит несколько неровно. Кроме того, этот подход тяжело сопровождать, так как имена методов и аргументов могут изменяться. Большинство программистов со временем склоняются к использованию первого стиля.
Упростите поиск конца строки с продолжением Одна из проблем пока- занного выше подхода — сложность поиска конца каждой строки. Альтернативой может служить размещение каждого элемента на отдельной строке и выделение окончания всей группы с помощью закрывающей скобки (листинг 31-44).
Листинг 31-44. Пример форматирования переноса строк в вызовах методов,
в котором каждый аргумент размещается на отдельной строке (Java)
DrawLine(
window.north,
window.south,
window.east,
window.west,
currentWidth,
currentAttribute
);
SetFontAttributes(
faceName[ fontId ],
size[ fontId ],
bold[ fontId ],
italic[ fontId ],
syntheticAttribute[ fontId ].underline,
syntheticAttribute[ fontId ].strikeout
);
Ясно, что такой подход требует много места. Однако если аргументами функции являются длинные названия полей объектов или имена указателей, например, такие,
как два последних в приведенном примере, размещение одного аргумента в строке существенно улучшает читаемость. Знаки
); в конце блока делают заметным его окончание. Вам также не придется переформатировать код при добавлении па- раметра — вы просто добавите новую строку.
На практике только небольшая часть методов нуждается в разбиении на строки.
Остальные можно располагать на одной строке. Любой из трех вариантов фор-

1   ...   83   84   85   86   87   88   89   90   ...   104


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