Файл: Руководство по стилю программирования и конструированию по.pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 30.11.2023
Просмотров: 851
Скачиваний: 2
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
342
ЧАСТЬ IV Операторы
Этот способ требует создания новых переменных, нового кода инициализации и нового кода проверки ошибок, что увеличивает возможность добавления ошибок.
Преимущества этого подхода должны сравниваться с привнесенной им сложнос- тью и увеличением вероятности появления вторичных ошибок.
14.2. Операторы, следующие
в произвольном порядке
Вам могут встречаться ситуации, когда кажется, что порядок выполнения несколь- ких выражений или нескольких блоков кода не имеет значения. Одно выражение не зависит от другого и логически из него не следует. Но поскольку упорядочен- ность влияет на читабельность, производительность и качество сопровождения,
вы можете использовать второстепенные критерии для определения порядка сле- дования выражений или блоков кода. Главный принцип — это Принцип Схожес- ти:
Располагайте взаимосвязанные действия вместе.
Размещение кода для чтения сверху вниз
Основная идея в том, что необходимо позволить читать программу сверху вниз,
а не перескакивая с места на место. Эксперты согласны, что порядок просмотра кода сверху вниз способствует улучшению читабельности. Однако простого раз- мещения последовательности команд сверху вниз недостаточно. Если тому, кто читает ваш код, приходится просматривать всю программу в поиске необходи- мой информации, то такой код нужно реорганизовать. Рассмотрим пример:
Пример плохого кода, в котором приходится перескакивать с места на место (C++)
MarketingData marketingData;
SalesData salesData;
TravelData travelData;
travelData.ComputeQuarterly();
salesData.ComputeQuarterly();
marketingData.ComputeQuarterly();
salesData.ComputeAnnual();
marketingData.ComputeAnnual();
travelData.ComputeAnnual();
salesData.Print();
travelData.Print();
marketingData.Print();
Допустим, вы хотите выяснить, как рассчитывается
marketingData. Вам придется начать с последней строки и проследить все упоминания
marketingData вплоть до первой строки.
marketingData встречается только в нескольких других местах,
но вы должны помнить, как
marketingData используется в каждом случае между
ГЛАВА 14 Организация последовательного кода
343
первым и последним своим упоминанием. Иначе говоря, вам придется просмот- реть и осмыслить каждую строку кода в этом фрагменте, чтобы выяснить, как вычисляется
marketingData. И, разумеется, этот пример гораздо проще, чем код,
встречающийся в реальных системах. Вот тот же код, но лучше организованный:
Пример хорошего, последовательного кода, который читается сверху вниз (C++)
MarketingData marketingData;
marketingData.ComputeQuarterly();
marketingData.ComputeAnnual();
marketingData.Print();
SalesData salesData;
salesData.ComputeQuarterly();
salesData.ComputeAnnual();
salesData.Print();
TravelData travelData;
travelData.ComputeQuarterly();
travelData.ComputeAnnual();
travelData.Print();
Этот код лучше по нескольким причинам. Упоминания каж- дой переменной располагаются вместе — они «локализова- ны». Число строк кода, в которых объекты являются «живы- ми», невелико. И, возможно, самое важное: код теперь вы- глядит так, что его можно разбить на отдельные методы для данных по маркетингу, продажам и поездкам. Первый фраг- мент не содержал подсказки, что эта декомпозиция возможна.
Группировка взаимосвязанных выражений
Размещайте взаимосвязанные выражения вместе. Они мо- гут быть связаны, так как работают с одними и теми же дан- ными, выполняют схожие задачи или зависят от порядка выполнения друг друга.
Есть простой способ убедиться, что взаимосвязанные выра- жения хорошо сгруппированы. Распечатайте текст вашего метода и обведите рамкой взаимосвязанные выражения. Если они хорошо упоря- дочены, вы получите картинку, похожую на рис. 14-1, где рамки не перекрываются.
Перекрестная ссылка Более формальное определение «жи- вых» переменных см. в подраз- деле «Измерение времени жиз- ни переменной» раздела 10.4.
Перекрестная ссылка Если вы придерживаетесь Процесса Про- граммирования с Псевдокодом,
ваш код будет автоматически груп- пироваться в блоки взаимосвязан- ных выражений (см. главу 9).
344
ЧАСТЬ IV Операторы
Рис. 14-1. Если код хорошо организован в группы, то рамки вокруг
взаимосвязанных разделов не перекрываются; они могут быть вложенными
Если выражения плохо организованы, вы получите картинку,
похожую на рис. 14-2, где рамки перекрываются. Если выяснится,
что перекрытие происходит, реорганизуйте ваш код, чтобы взаимосвязанные выражения были лучше сгруппированы.
Рис. 14-2. Если код организован неудачно, то рамки вокруг связанных
разделов пересекаются
После того, как вы сгруппируете взаимосвязанные выражения, может выяснить- ся, что они сильно связаны между собой, а к предшествующему и последующему коду не имеют значимого отношения. В этом случае, возможно, следует выделить эти выражения в отдельный метод.
Контрольный список: организация
последовательного кода
Способствует ли код выявлению зависимостей между выражениями?
Способствуют ли имена методов выявлению зависимостей?
Способствуют ли параметры методов выявлению зависимостей?
Описывают ли комментарии такие зависимости, которые иначе не будут явными?
Используются ли вспомогательные переменные для проверки последователь- ных действий в критических частях кода?
Возможно ли прочтение кода сверху вниз?
Сгруппированы ли вместе взаимосвязанные выражения?
Перенесены ли относительно независимые группы выражений в отдельные методы?
Перекрестная ссылка Об объе- динении операций над перемен- ными см. раздел 10.4.
http://cc2e.com/1472
1 ... 39 40 41 42 43 44 45 46 ... 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;
}
>