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

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


Скачать 7.6 Mb.
НазваниеРуководство по стилю программирования и конструированию по
Дата18.05.2023
Размер7.6 Mb.
Формат файлаpdf
Имя файлаCode_Complete.pdf
ТипРуководство
#1139697
страница45 из 104
1   ...   41   42   43   44   45   46   47   48   ...   104
ГЛАВА 15 Условные операторы
355
Пример использования операторов if-then-else вместо искусственной переменной
для оператора case — хорошая практика (Java)
if ( UserCommand.equals( COMMAND_STRING_COPY ) ) {
Copy();
}
else if ( UserCommand.equals( COMMAND_STRING_DELETE ) ) {
DeleteCharacter();
}
else if ( UserCommand.equals( COMMAND_STRING_FORMAT ) ) {
Format();
}
else if ( UserCommand.equals( COMMAND_STRING_HELP ) ) {
Help();
}
else {
HandleUserInputError( ErrorType_InvalidCommandInput );
}
Используйте вариант по умолчанию только для обработки настоящих
значений по умолчанию Порой, когда остается единственный вариант, вы ре- шаете закодировать его как вариант по умолчанию. Заманчиво, но... неразумно: вы лишаетесь автоматического документирования, предоставляемого метками опе- ратора
case, и теряете возможность определения ошибок с помощью варианта по умолчанию.
Такие операторы
case подвержены ошибкам при модификации. Если вы исполь- зуете настоящее значение по умолчанию, то добавление нового варианта триви- ально: вы просто дописываете новую метку и соответствующий код. Если же зна- чение по умолчанию искусственно, провести изменения сложнее. Вам придется добавить новый вариант, возможно, сделав его новым умолчанием, а затем поме- нять прежний вариант по умолчанию так, чтобы он стал обычным вариантом.
Поэтому изначально используйте только настоящие значения по умолчанию.
Используйте вариант по умолчанию для выявления ошибок Если вариант по умолчанию в операторе
case не используется и не планируется для иных дей- ствий, разместите в нем диагностическое сообщение:
Пример использования варианта по умолчанию
для выявления ошибок — хорошая практика (Java)
switch ( commandShortcutLetter ) {
case ‘a’:
PrintAnnualReport();
break;
case ‘p’:
// Никаких действий не требуется, но этот вариант предусмотрен.
break;
case ‘q’:
PrintQuarterlyReport();
break;

356
ЧАСТЬ IV Операторы case ‘s’:
PrintSummaryReport();
break;
default:
DisplayInternalError( “Internal Error 905: Call customer support.” );
}
Такие сообщения полезны и в отладочном, и в промышленном коде. Большинство пользователей предпочитают сообщения вроде «Внутренняя ошибка: Пожалуйста,
позвоните в службу поддержки» отказу системы или, что еще хуже, немного не- верным результатам, которые выглядят вполне нормально до того, как их прове- рит начальник.
Если вариант по умолчанию служит не для выявления ошибок, а для иных целей,
то подразумевается, что любой выбор будет корректным. Дважды проверьте ваш код и убедитесь, что любое возможное значение на входе оператора
case будет допустимо. Если обнаружится какой-то некорректный вариант, перепишите вы- ражения, чтобы условие по умолчанию проверяло ошибки.
В C++ и Java старайтесь не писать код, проваливающийся сквозь блоки
оператора case C-подобные языки (C, C++ и Java) не выполняют выход из блока
case автоматически. Вместо этого вы должны явно закодировать конец блока. Если этого не сделать, программа провалится сквозь этот блок и начнет выполнять следующий. Это иногда приводит к некоторым вопиющим способам программи- рования, включая следующий пример:
Пример неправильного использования
оператора case (C++)
switch ( InputVar ) {
case ‘A’: if ( test ) {
// оператор 1
// оператор 2
case ‘B’: // оператор 3
// оператор 4
}
break;
}
Этот способ плох, так как приводит к перемешиванию уп- равляющих конструкций. Разобраться во вложенных управ- ляющих конструкциях довольно тяжело, а в перекрываю- щихся — практически невозможно. Модификация вариан- тов
‘A’ или ‘B’ будет посложнее хирургической операции на головном мозге, и вполне вероятно, что их придется пол- ностью переписать, чтобы получить работоспособную вер- сию. Что вы и можете сделать с самого начала. В общем,
лучше избегать сквозного перехода в блоках оператора
case.
Перекрестная ссылка Такое форматирование кода делает пример лучше, чем он есть на самом деле. О том, как сделать,
чтобы хороший код выглядел хорошо, а плохой — плохо, см.
полдраздел «Разметка концов строк» раздела 31.3 и оставшу- юся часть главы 31.

ГЛАВА 15 Условные операторы
357
В C++ ясно и безошибочно обозначайте код, проваливающийся сквозь блоки
оператора case Если вы намеренно написали код, который должен выполняться в нескольких блоках
case подряд, внятно прокомментируйте место, где это про- исходит, и объясните, почему это должно быть закодировано таким способом:
Пример документирования сквозного выполнения блоков case (C++)
switch ( errorDocumentationLevel ) {
case DocumentationLevel_Full:
DisplayErrorDetails( errorNumber );
// СКВОЗНОЙ ПЕРЕХОД — Полная документация также печатает
// суммарные комментарии.
case DocumentationLevel_Summary:
DisplayErrorSummary( errorNumber );
// СКВОЗНОЙ ПЕРЕХОД — Суммарная документация
// также печатает номер ошибки.
case DocumentationLevel_NumberOnly:
DisplayErrorNumber( errorNumber );
break;
default:
DisplayInternalError( “Internal Error 905: Call customer support.” );
}
Эта методика встречается так же часто, как и люди, которые предпочитают по- держанный «Понтиак Ацтек» новенькому «Корвету». Обычно код, переходящий сквозь один блок
case к другому, просто напрашивается на ошибки при модифи- кации и его следует избегать.
Контрольный список: использование
условных операторов
Операторы if-then
 Очевиден ли номинальный путь выполнения кода?
 Правильно ли выполняется ветвление при проверке if-then на равенство?
 Присутствует и задокументирован ли блок else?
 Корректен ли блок else?
 Правильно ли расположены блоки if и else — нет ли инверсии?
 Следует ли нормальный вариант после if, а не после else?
Последовательности if-then-else-if
 Преобразуются ли сложные проверки в вызовы логических функций?
 Проверяются ли наиболее вероятные случаи первыми?
 Все ли варианты учитываются?
 Является ли последовательность if-then-else-if лучшей реализацией или лучше использовать оператор case?
http://cc2e.com/1545

358
ЧАСТЬ IV Операторы
Операторы case
 Разумно ли отсортированы варианты в операторе case?
 Сделаны ли действия, выполняемые для каждого варианта, простыми, на- пример, с помощью преобразования в методы в случае необходимости?
 Проверяет ли оператор case реальную переменную, а не искусственно со- зданную, приводящую к неправильному использованию оператора case?
 Корректны ли значения, обрабатываемые в блоке по умолчанию?
 Используется ли блок по умолчанию для выявления ошибок и сообщения о непредвиденных ситуациях?
 В языках C, C++ или Java содержит ли каждый блок case команды для выхода?
Ключевые моменты

В простых выражениях
if-else обращайте внимание на порядок блоков if и else,
особенно если они обрабатывают множество ошибок. Убедитесь, что номиналь- ный вариант прослеживается ясно.

Для последовательностей
if-then-else и операторов case выбирайте порядок,
позволяющий улучшить читабельность.

Для перехвата ошибок используйте блок по умолчанию в операторе
case или последний блок
else в цепочке операторов if-then-else.

Управляющие конструкции не равнозначны. Выбирайте конструкцию, наибо- лее подходящую для данного участка кода.

ГЛАВА 16 Циклы
359
Г Л А В А 1 6
Циклы
Содержание

16.1. Выбор типа цикла

16.2. Управление циклом

16.3. Простое создание цикла — изнутри наружу

16.4. Соответствие между циклами и массивами
Связанные темы

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

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

Код с условными операторами: глава 15

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

Отношения между управляющими структурами и типами данных: раздел 10.7
Цикл — это неформальное обозначение любой структуры итеративного типа,
т. е. такой, которая заставляет программу повторно выполнять некий блок кода.
Наиболее распространенными видами циклов являются
for, while и do-while в C++
и Java,
For-Next, While-Wend и Do-Loop-While — в Microsoft Visual Basic. Использо- вание циклов — один из наиболее сложных аспектов программирования. Знание,
как и когда применять каждый тип цикла, — это решающий фактор в конструи- ровании высококачественного ПО.
16.1. Выбор типа цикла
В большинстве языков можно использовать несколько видов циклов, перечислен- ных ниже.

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

Постоянно вычисляемый цикл не знает заранее, сколько раз он будет выпол- няться и проверяет необходимость завершения при каждой итерации. Напри- мер, он выполняется, пока остаются деньги, пока пользователь не выберет команду завершения или не встретится ошибка.
http://cc2e.com/1609

360
ЧАСТЬ IV Операторы

Бесконечный цикл выполняется все время с момента старта. Такие циклы можно встретить во встроенных системах, таких как кардиостимуляторы, микровол- новые печи и автопилоты.

Цикл с итератором выполняет некоторые действия однократно для каждого элемента контейнерного класса.
Первое, чем отличаются эти циклы друг от друга, — это гибкость в определении числа итераций: выполняется ли цикл указанное количество раз или проверяет необходимость завершения на каждом шаге.
Кроме того, эти варианты отличаются расположением проверки завершения. Вы можете поместить проверку в начало, середину или конец цикла. Эта характери- стика определяет, будет ли цикл выполняться хоть раз. Если условие цикла про- веряется в начале, то его тело не обязательно будет выполняться. Если цикл про- веряется в конце, то его тело выполняется минимум один раз. Если же проверка находится в середине, то часть цикла, предшествующая ей, выполняется не менее раза, а код, следующий за проверкой, не обязательно будет выполняться.
Гибкость и расположение проверки условия определяют тип цикла, выбираемый в качестве управляющей структуры. В табл. 16-1 показаны разновидности циклов в нескольких языках программирования и характеризуются их гибкость и распо- ложение проверки.
Табл. 16-1. Типы циклов
Язык
Тип цикла
Гибкость
Место проверки
Visual Basic
For-Next
Нет
Начало
While-Wend
Да
Начало
Do-Loop-While
Да
Начало или конец
For-Each
Нет
Начало
C, C++, C#, Java
for
Да
Начало
while
Да
Начало
do-while
Да
Конец
foreach
1
Нет
Начало
Когда использовать цикл while
Новички иногда считают, что условие цикла
while проверяется постоянно и цикл завершается в тот момент, когда это условие становится ложным, независимо от того, какой оператор в это время выполняется (Curtis et al, 1986). Хотя цикл
while
и не столь гибок, он все же является гибким вариантом цикла. Если вы заранее не знаете, сколько итераций должен выполнить цикл, используйте
while. Вопреки тому что думают новички, проверка выхода из цикла выполняется только раз за ите- рацию, и главным вопросом в отношении циклов
while является выбор места этой проверки — в начале или конце цикла.
1
Реализован только в C#. На момент написания книги планируется в других языках, включая Java.

ГЛАВА 16 Циклы
361
Цикл с проверкой в начале
В качестве цикла с проверкой в начале вы можете использовать цикл
while в язы- ках C++, C#, Java, Visual Basic и многих других. Вы также можете эмулировать цикл
while в других языках.
Цикл с проверкой в конце
Время от времени вам требуется гибкий цикл, который должен выполняться хотя бы раз. В этом случае можно применить
while с проверкой условия в конце цик- ла. Вы можете использовать варианты
do-while в C++, C# и Java, Do-Loop-While вVisual
Basic или эмулировать циклы с проверкой в конце в других языках
Когда использовать цикл с выходом
В цикле с выходом условие выхода содержится внутри тела цикла, а не в его на- чале или конце. Циклы с выходом реализованы исключительно в Visual Basic, но вы можете эмулировать их, используя структурированные конструкции
while и break
в C++, C и Java или операторы
goto в других языках.
Нормальные циклы с выходом
Цикл с выходом обычно состоит из начала цикла, тела цикла (включая условие выхода) и конца цикла:
Пример обычного цикла с выходом (Visual Basic)
Do
Операторы.
If ( some exit condition ) Then Exit Do
Еще операторы.
Loop
Обычно цикл с выходом нужен, когда проверка условия в начале или конце цик- ла требует кодирования полутора циклов. Вот пример на C++, в котором нужен цикл с выходом, но он не используется:
Пример дублированного кода, который подвержен ошибкам при сопровождении (C++)
// Вычисляем счет и рейтинги.
score = 0;
Эти строки появляются здесь...
GetNextRating( &ratingIncrement );
rating = rating + ratingIncrement;
while ( ( score < targetScore ) && ( ratingIncrement != 0 ) ) {
GetNextScore( &scoreIncrement );
score = score + scoreIncrement;
>
>
>

362
ЧАСТЬ IV Операторы
…и повторяются здесь.
GetNextRating( &ratingIncrement );
rating = rating + ratingIncrement;
}
Первые две строки в начале примера повторяются в последних двух строках цикла
while. При модификации вы легко можете забыть о поддержании двух параллель- ных наборов строк. Другой программист, изменяющий код, возможно, и не дога- дается, что эти строки должны сохраняться одинаковыми. В любом случае резуль- татом будут ошибки, возникшие из-за неполной модификации. Вот как можно переписать код более ясно:
Пример цикла с выходом, более легкого в сопровождении (C++)
// Вычисляем счет и рейтинги. Этот код использует бесконечный цикл
// и оператор break для имитации цикла с выходом.
score = 0;
while ( true ) {
GetNextRating( &ratingIncrement );
rating = rating + ratingIncrement;
Это условие выхода из цикла (и теперь оно может быть упрощено с помощью теорем ДеМоргана,
описанных в разделе 19.1).
if ( !( ( score < targetScore ) && ( ratingIncrement != 0 ) ) ) {
break;
}
GetNextScore( &scoreIncrement );
score = score + scoreIncrement;
}
Вот как тот же код можно написать на Visual Basic:
Пример цикла с выходом (Visual Basic)
‘ Вычисляем счет и рейтинги.
score = 0
Do
GetNextRating( ratingIncrement )
rating = rating + ratingIncrement
If ( not ( score < targetScore and ratingIncrement <> 0 ) ) Then Exit Do
GetNextScore( ScoreIncrement )
score = score + scoreIncrement
Loop
При использовании этого типа циклов учитывайте следующие тонкие моменты.
>
>

ГЛАВА 16 Циклы
363
Разместите все условия выхода в одном месте. Распростра- нение их по коду практически гарантирует, что то или иное условие завершения будет пропущено при отладке, модифи- кации или тестировании.
Пишите комментарии для ясности. Если вы применяете цикл с выходом в языке, который не поддерживает его напрямую,
используйте комментарии, чтобы сделать свои действия очевидными.
Цикл с выходом — структурированная управляющая конструкция, име- ющая один вход и один выход. Такая структура является предпочтитель- ным вариантом цикла (Software Productivity Consortium, 1989). Доказано,
что этот тип цикла легче для понимания, чем другие. Группа студентов-програм- мистов сравнила такой цикл с другими вариантами, имеющими выход в начале или конце (Soloway, Bonar и Ehrlich, 1983). Тесты на понимание для цикла с выхо- дом выполнялись студентами на 25% успешнее. Авторы курса пришли к выводу,
что структура цикла с выходом лучше, чем другие циклы, моделирует способ че- ловеческого представления итеративного процесса.
В повседневной практике цикл с выходом пока еще не широко распространен.
Присяжные все еще заперты в накуренной комнате, споря о том, годиться ли эта методика для промышленного кода. Пока они там томятся, цикл с выходом будет хорошим инструментом в вашем программистском наборе — при условии его ак- куратного использования.
Аномальные циклы с выходом
Другой вид цикла с выходом служит для замены следующего варианта «полутор- ного» цикла:
Пример входа в середину цикла с помощью goto
плохая практика (C++)
goto Start;
while ( expression ) {
// Делаем что-то.
Start:
// Делаем что-то еще.
}
На первый взгляд, этот цикл похож на предыдущие примеры цикла с выходом. Он используется, если выражение, обозначенное как
// делаем что-то, не должно выполняться при первом проходе цикла, а выражение
// делаем что-то еще
должно. Это тоже конструкция с одним входом и выходом: единственный вход в цикл — через оператор
goto в начале, а выход — с помощью условия while. Этот подход содержит две проблемы: он использует
goto и довольно необычен, чем сби- вает с толку.
Перекрестная ссылка Другие сведения об условиях заверше- ния представлены ниже в этой главе. Об использовании ком- ментариев в циклах см. подраз- дел «Комментирование управля- ющих структур» раздела 32.5.

364
ЧАСТЬ IV Операторы
В C++ вы можете добиться того же эффекта без использования
goto, как показано в следующем примере. Если язык не поддерживает команду
break, вы можете эму- лировать ее, применив
goto.
Пример кода, переписанного без использования goto — лучший вариант (C++)
while ( true ) {
Блоки перед и после
break поменяны местами.
// Делаем что-то еще.
if ( !( expression ) ) {
break;
}
// Делаем что-то.
}
Когда использовать цикл for
Цикл
for — хороший вариант, если вам нужен цикл, выпол- няющийся определенное количество раз. Вы можете исполь- зовать
for в C++, C, Java, Visual Basic и большинстве других языков.
Применяйте циклы
for в простых случаях, не требующих управления изнутри тела цикла. Используйте их, когда управление циклом заклю- чается в простом инкременте или декременте, скажем, при проходе по элемен- там контейнера. Особенность цикла
for в том, что его надо настроить в начале выполнения и забыть о нем. Вам ничего не надо делать внутри него для управле- ния его работой. Если существует условие, по которому выполнение цикла пре- рывается изнутри, вместо
for используйте конструкцию while.
Не изменяйте значение индекса цикла
for явно, чтобы принудительно его завершить.
Вместо этого используйте
while. Цикл for предназначен для простых случаев. Более сложные задачи организации циклов лучше решать с помощью цикла
while.
Когда использовать цикл foreach
Цикл
foreach (или его эквиваленты For-Each в Visual Basic, for-in в Python) полезен для выполнения действий над каждым элементом массива или другого контейне- ра. Его преимущество в том, что он позволяет обойтись без вспомогательной ариф- метики для обслуживания цикла и, таким образом, избежать ошибок. Вот пример такого цикла:
Дополнительные сведения О
других хороших приемах исполь- зования циклов for см. в «Writ- ing Solid Code» (Maguire, 1993).

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


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