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

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


Скачать 7.6 Mb.
НазваниеРуководство по стилю программирования и конструированию по
Дата18.05.2023
Размер7.6 Mb.
Формат файлаpdf
Имя файлаCode_Complete.pdf
ТипРуководство
#1139697
страница44 из 104
1   ...   40   41   42   43   44   45   46   47   ...   104
ГЛАВА 14 Организация последовательного кода
345
Ключевые моменты

Главный принцип организации последовательного кода — упорядочение за- висимостей.

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

Если порядковые зависимости в коде отсутствуют, старайтесь размещать вза- имосвязанные выражения как можно ближе друг к другу.

346
ЧАСТЬ IV Операторы
Г Л А В А 1 5
Условные операторы
Содержание

15.1. Операторы
if

15.2. Операторы
case
Связанные темы

Укрощение глубокой вложенности: раздел 19.4

Общие вопросы управления: глава 19

Код с операторами цикла: глава 16

Последовательный код: глава 14

Отношения между типами данных и управляющими структурами: раздел 10.7
Условный оператор управляет выполнением других операторов. Их выполнение
«обусловливают» такие операторы, как
if, else, case и switch. Хотя операторы цик- ла, например
while и for, по смыслу тоже можно отнести к условным, обычно их рассматривают отдельно. Операторы
while и for мы обсудим в главе 16.
15.1. Операторы if
В зависимости от выбранного языка программирования вы можете использовать несколько видов
if-операторов. Простейшие из них — if или if-then. Оператор if-
then-else немного сложнее, а наибольшую сложность представляют последователь- ности
if-then-else-if.
Простые операторы if-then
Следуйте этим правилам при написании
if-выражений.
Сначала напишите код номинального хода алгоритма, затем
опишите исключительные случаи Пишите код так, чтобы нормаль- ный путь выполнения был очевиден. Убедитесь, что нестандартные об- стоятельства не затмевают смысл основного алгоритма. Это важно как с точки зрения читабельности, так и с точки зрения производительности.
Убедитесь, что при сравнении на равенство ветвление корректно Исполь- зование > вместо >= или < вместо <= — это аналог ошибки потери единицы при http://cc2e.com/1538

ГЛАВА 15 Условные операторы
347
обращении к массиву или вычислении индекса цикла. Чтобы ее избежать, в опе- раторе цикла следует рассматривать граничные точки, а в условных операторах
— учитывать случаи равенства.
Размещайте нормальный вариант после if, а не пос-
ле else Пишите код так, чтобы нормальный вариант разви- тия событий обрабатывался в первую очередь. Это совпада- ет с главным принципом размещения действий, являющих- ся результатом выбора, как можно ближе к точке этого вы- бора. Вот пример кода, который выполняет неоднократную проверку ошибок, бес- порядочно разбросанную по тексту:
Пример кода, который беспорядочно обрабатывает
многочисленные ошибки (Visual Basic)
OpenFile( inputFile, status )
If ( status = Status_Error ) Then
Ошибочная ситуация.
errorType = FileOpenError
Else
Нормальная ситуация.
ReadFile( inputFile, fileData, status )
If ( status = Status_Success ) Then
Нормальная ситуация.
SummarizeFileData( fileData, summaryData, status )
If ( status = Status_Error ) Then
Ошибочная ситуация.
errorType = ErrorType_DataSummaryError
Else
Нормальная ситуация.
PrintSummary( summaryData )
SaveSummaryData( summaryData, status )
If ( status = Status_Error ) Then
Ошибочная ситуация.
errorType = ErrorType_SummarySaveError
Else
Нормальная ситуация.
UpdateAllAccounts()
EraseUndoFile()
errorType = ErrorType_None
End If
End If
Else
Перекрестная ссылка Другие способы обращения с кодом обработки ошибок описаны в разделе 19.4.
>
>
>
>
>
>
>

348
ЧАСТЬ IV Операторы errorType = ErrorType_FileReadError
End If
End If
Этот код сложен для понимания, так как в нем перемешаны нормальные и оши- бочные ситуации. Тяжело проследить путь, проходимый в коде при нормальных обстоятельствах. Кроме того, так как ошибки иногда обрабатываются в блоке
if, а не
else, тяжело найти, в каких же ветвях if обрабатываются нормальные ситуации.
В переписанном примере нормальный путь последовательно кодируется первым,
а ошибочные ситуации — последними. Это упрощает поиск и чтение номиналь- ного варианта алгоритма.
Пример кода, который систематично обрабатывает
большое количество ошибок (Visual Basic)
OpenFile( inputFile, status )
If ( status = Status_Success ) Then
Нормальная ситуация.
ReadFile( inputFile, fileData, status )
If ( status = Status_Success ) Then
Нормальная ситуация.
SummarizeFileData( fileData, summaryData, status )
If ( status = Status_Success ) Then
Нормальная ситуация.
PrintSummary( summaryData )
SaveSummaryData( summaryData, status )
If ( status = Status_Success ) Then
Нормальная ситуация.
UpdateAllAccounts()
EraseUndoFile()
errorType = ErrorType_None
Else
Ошибочная ситуация.
errorType = ErrorType_SummarySaveError
End If
Else
Ошибочная ситуация.
errorType = ErrorType_DataSummaryError
End If
Else
Ошибочная ситуация.
errorType = ErrorType_FileReadError
End If
Else
>
>
>
>
>
>
>

ГЛАВА 15 Условные операторы
349
Ошибочная ситуация.
errorType = ErrorType_FileOpenError
End If
Здесь можно проследить главное направление
if-проверок, чтобы выяснить нор- мальный вариант событий. Этот фрагмент позволяет фокусировать чтение в ос- новном направлении, а не преодолевать исключительные ситуации, поэтому этот код в целом читабельнее. Стек ошибочных условий, расположенный внизу вло- жения, — признак хорошо написанного кода обработки ошибок.
Этот пример иллюстрирует один систематический подход к обработке нормаль- ных и ошибочных ситуаций. Другие решения этой проблемы: использование пре- дохранительных конструкций, диспетчеризация полиморфных объектов, выне- сение внутренних проверок в отдельные методы — обсуждаются на протяжении всей книги. Полный список существующих подходов см. в разделе 19.4.
Размещайте осмысленные выражения после оператора if Иногда можно встретить код, в котором блок
if пуст:
Пример пустого блока if (Java)
if ( SomeTest )
;
else {
// делаем что-то
}
Опытные программисты не станут писать такой код хотя бы затем, чтобы избежать лишней работы по вводу пустой стро- ки и оператора
else. Этот код выглядит глупо и может быть легко улучшен путем отрицания предиката в выражении
if,
перемещения кода из блока
else в блок if и удаления блока
else. Вот как будет выглядеть код после таких изменений:
Пример преобразования пустого блока if (Java)
if ( ! someTest ) {
// делаем что-то
}
Рассмотрите вопрос использования блока else Если вы считаете,
что вам нужен простой оператор
if, подумайте, может, вам на самом деле нужен вариант с
if-then-else. Классический анализ General Motors пока- зал, что в 50–80% случаев использования операторов
if следовало применять и оператор
else (Elshoff, 1976).
Одна из причин добавления блока
else — даже пустого — в том, чтобы продемон- стрировать, что вариант с
else был учтен. Конечно, кодирование пустых выраже- ний в
else просто для того, чтобы показать, что этот вариант рассмотрен, может
Перекрестная ссылка Один из ключей к конструированию эф- фективного оператора if- — со- здание правильного управляюще- го логического выражения. Об эф- фективном применении логичес- ких выражений см. раздел 19.1.
>

350
ЧАСТЬ IV Операторы быть преувеличением, но хотя бы принимайте вариант с
else во внимание. Если вы задаете
if-проверку, не имеющую блока else, то, кроме очевидных случаев, пи- шите в комментариях объяснение, почему
else отсутствует, скажем, так:
Пример полезного, прокомментированного блока elsе (Java)
// Если цвет задан корректно.
if ( COLOR_MIN <= color && color <= COLOR_MAX ) {
// Делаем что-то
}
else {
// Иначе цвет задан некорректно.
// Вывод на экран не выполняется –- просто игнорируем команду.
}
Проверяйте корректность выражения else При тестировании кода вы мо- жете решить, что достаточно проверить основной блок
if и все. Однако, если можно проверить вариант в
else, не забудьте это сделать.
Проверяйте возможную перестановку блоков if и else Частая ошибка при программировании выражений
if-then состоит в размещении кода из блока if в блоке
else, т. е. инвертировании логики выражения if. Проверяйте ваш код на наличие этой ошибки.
Последовательности операторов if-then-else
Если язык не поддерживает операторы
case или поддерживает их только частич- но, вам часто придется писать последовательные проверки
if-then-else. К приме- ру, код распределения символов по категориям, может выглядеть в виде такой цепочки:
Пример использования последовательности
if-then-else для распределения символов
по категориям (C++)
if ( inputCharacter < SPACE ) {
characterType = CharacterType_ControlCharacter;
}
else if (
inputCharacter == ‘ ‘ ||
inputCharacter == ‘,’ ||
inputCharacter == ‘.’ ||
inputCharacter == ‘!’ ||
inputCharacter == ‘(‘ ||
inputCharacter == ‘)’ ||
inputCharacter == ‘:’ ||
inputCharacter == ‘;’ ||
inputCharacter == ‘?’ ||
inputCharacter == ‘-’
) {
characterType = CharacterType_Punctuation;
}
Перекрестная ссылка Об упро- щении сложных выражений см.
раздел 19.1.

ГЛАВА 15 Условные операторы
351
else if ( ‘0’ <= inputCharacter && inputCharacter <= ‘9’ ) {
characterType = CharacterType_Digit;
}
else if (
( ‘a’ <= inputCharacter && inputCharacter <= ‘z’ ) ||
( ‘A’ <= inputCharacter && inputCharacter <= ‘Z’ )
) {
characterType = CharacterType_Letter;
}
Учитывайте советы, приведенные далее, при написании последовательных
if-then-else.
Упрощайте сложные проверки с помощью вызовов логических функций
Одна из причин, по которой код из предыдущего примера сложно читать, в том,
что проверки категорий символов довольно сложны. Для улучшения читабельно- сти вы можете заменить их вызовами функций, возвращающих логические зна- чения. Вот как этот пример может выглядеть после замены условий логическими функциями:
Пример последовательности if-then-else,
использующей вызовы логических функций (C++)
if ( IsControl( inputCharacter ) ) {
characterType = CharacterType_ControlCharacter;
}
else if ( IsPunctuation( inputCharacter ) ) {
characterType = CharacterType_Punctuation;
}
else if ( IsDigit( inputCharacter ) ) {
characterType = CharacterType_Digit;
}
else if ( IsLetter( inputCharacter ) ) {
characterType = CharacterType_Letter;
}
Размещайте наиболее вероятные варианты раньше остальных Помес- тив в начало наиболее часто встречающиеся ситуации, вы минимизируете то ко- личество кода, обрабатывающего исключительные случаи, которое придется про- читать при поиске обычных вариантов. Вы увеличите эффективность, потому что уменьшите число проверок, выполняемых кодом в большинстве случаев. В при- веденном примере буквы обычно встречаются чаще, чем знаки пунктуации, но проверка этих знаков написана первой. Вот как исправить код, чтобы буквы про- верялись в первую очередь:
Пример проверки прежде всего наиболее часто
встречающихся вариантов (C++)
Этот случай встречается чаще других, поэтому проверяем его первым.
if ( IsLetter( inputCharacter ) ) {
characterType = CharacterType_Letter;
}
>

352
ЧАСТЬ IV Операторы else if ( IsPunctuation( inputCharacter ) ) {
characterType = CharacterType_Punctuation;
}
else if ( IsDigit( inputCharacter ) ) {
characterType = CharacterType_Digit;
}
Этот случай наиболее редкий, поэтому проверяем его последним.
else if ( IsControl( inputCharacter ) ) {
characterType = CharacterType_ControlCharacter;
}
Убедитесь, что учтены все варианты Закодируйте в последнем блоке
else сообщение об ошибке или утвержде- ние, чтобы отловить ситуации, которые вы не планирова- ли. Это сообщение об ошибке предназначено скорее вам, а не пользователю, так что сформулируйте его соответственно.
Вот как модифицировать пример классификации символов для выполнения проверки «других вариантов»:
Пример использования варианта по умолчанию для перехвата ошибок (C++)
if ( IsLetter( inputCharacter ) ) {
characterType = CharacterType_Letter;
}
else if ( IsPunctuation( inputCharacter ) ) {
characterType = CharacterType_Punctuation;
}
else if ( IsDigit( inputCharacter ) ) {
characterType = CharacterType_Digit;
}
else if ( IsControl( inputCharacter ) ) {
characterType = CharacterType_ControlCharacter;
}
else {
DisplayInternalError( “Unexpected type of character detected.” );
}
Замените последовательности if-then-else другими конструкциями, кото-
рые поддерживает ваш язык программирования Некоторые языки, скажем,
Microsoft Visual Basic и Ada, предоставляют операторы
case, поддерживающие стро- ки, перечисления и логические функции. Используйте их — они проще в написа- нии и чтении, чем цепочки
if-then-else. Код классификации типов символов, на- писанный на Visual Basic с помощью оператора
case, может выглядеть так:
Пример использования оператора case
вместо последовательности if-then-else (Visual Basic)
Select Case inputCharacter
Case “a” To “z”
characterType = CharacterType_Letter
Перекрестная ссылка Это так- же хороший пример того, как использовать последователь- ность if-then-else вместо кода глубокой вложенности. Об этой методике см. раздел 19.4.
>

ГЛАВА 15 Условные операторы
353
Case “ “, “,”, “.”, “!”, “(“, “)”, “:”, “;”, “?”, “-”
characterType = CharacterType_Punctuation
Case “0” To “9”
characterType = CharacterType_Digit
Case FIRST_CONTROL_CHARACTER To LAST_CONTROL_CHARACTER
characterType = CharacterType_Control
Case Else
DisplayInternalError( “Unexpected type of character detected.” )
End Select
15.2. Операторы case
Оператор
case или switch — конструкция, сильно варьирующаяся от языка к язы- ку. C++ и Java поддерживают
case только для порядковых типов, рассматривая по одному значению за раз. Visual Basic поддерживает
case для порядковых типов и предоставляет мощные средства для обозначения диапазонов и комбинаций зна- чений. Многие языки сценариев вообще не поддерживают
case.
Следующие разделы представляют основные принципы эффективного использо- вания операторов
case.
Выбор наиболее эффективного порядка вариантов
Организовать порядок следования вариантов в операторе
case можно по-разно- му. Если выражение
case невелико и содержит только три варианта и три соот- ветствующих строки кода, то выбранный порядок большого значения не имеет.
Однако в случае длинного оператора
case (скажем, обрабатывающего десятки различных событий в программе, управляемой событиями) порядок следования достаточно важен. Давайте рассмотрим способы упорядочения.
Упорядочивайте варианты по алфавиту или численно Если все варианты равнозначны, их размещение в алфавитном порядке улучшает читабельность.
В этом случае нужный вариант легко выбрать из группы.
Поместите правильный вариант первым Если у вас есть один корректный вариант и несколько исключений, поместите правильное значение первым. От- метьте в комментариях, что этот вариант является нормальным, а все остальные
— исключительными.
Отсортируйте варианты по частоте Поместите наиболее часто встреча- ющиеся случаи в начало, а более редкие — в конец списка. Этот подход имеет два преимущества. Первое: читатели легко обнаружат ситуации, встречающиеся чаще всего. Второе: при сканировании списка в поисках определенных значений ско- рее всего будут требоваться наиболее часто встречающиеся варианты, поэтому их размещение в начале кода сделает поиск быстрее.
Советы по использованию операторов case
Далее перечислены советы по применению операторов
case.
Сделайте обработку каждого варианта простой
Код, связанный с каждым вариантом, должен быть корот-
Перекрестная ссылка Другие советы по упрощению кода см.
в главе 24.

354
ЧАСТЬ IV Операторы ким. Краткость позволяет сделать структуру оператора
case прозрачнее. Если дей- ствия, предпринимаемые для какого-то варианта слишком сложны, напишите метод и вызывайте его, а не размещайте весь этот код прямо в блоке
case.
Не конструируйте искусственные переменные с целью получить возмож-
ность использовать оператор case Оператор case следует применять для про- стых данных, которые легко разбить на категории. Если ваши данные нельзя на- звать простыми, используйте цепочки
if-then-else. Фальшивые переменные сбива- ют с толки, и их следует избегать. Например, не делайте так:
Пример создания искусственной переменной
для оператора case — плохая практика (Java)
action = userCommand[ 0 ];
switch ( action ) {
case ‘c’:
Copy();
break;
case ‘d’:
DeleteCharacter();
break;
case ‘f’:
Format();
break;
case ‘h’:
Help();
break;
default:
HandleUserInputError( ErrorType.InvalidUserCommand );
}
Переменная
action управляет оператором case. В данном случае action конструи- руется с помощью первого символа строки
userCommand, которая вводится пользо- вателем.
Этот потенциально опасный код может привести к пробле- мам. Вообще, если вы фабрикуете специальную переменную для
case, реальные данные могут не вписываться в данное вы- ражение так, как вы это себе представляете. В приведенном примере, если пользователь набирает copy, в операторе
case
отрезается первый символ «
c», и корректно вызывается ме- тод
Copy(). С другой стороны, если пользователь набирает cement overshoes, clambake или cellulite,
case точно также отрежет символ «
c» и вызовет Copy(). Проверка ошибочных команд в блоке
else оператора case не будет нормально функционировать, так как будет обрабатывать только неправильные первые буквы, а не команды целиком.
Вместо создания искусственной переменной в этом коде лучше использовать последовательность
if-then-else-if и проверять целую строку. Вот как эффективно переписать код:
Перекрестная ссылка В проти- воположность этому совету иногда можно улучшить чита- бельность, присваивая значение сложного выражения хорошо названной логической перемен- ной или функции (см. подраз- дел «Упрощение сложных выра- жений» раздела 19.1).

1   ...   40   41   42   43   44   45   46   47   ...   104


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