Файл: Руководство по стилю программирования и конструированию по.pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 30.11.2023
Просмотров: 834
Скачиваний: 2
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
ГЛАВА 16 Циклы
377
// Промежуточный код.
for ( int recordCount = 0; recordCount < MAX_RECORDS; recordCount++ ) {
// Дополнительный циклический код, использующий другую переменную recordCount.
}
Такая методика полезна для документирования назначения переменной
recordCount.
Однако не полагайтесь на ваш компилятор в вопросе области видимости
record-
Count. В разделе 6.3.3.1 книги «The C++ Programming Language» (Stroustrup, 1997)
говорится, что переменная
recordCount должна иметь область видимости, ограни- ченную ее циклом. Но, проверив эту функциональность в трех разных компиля- торах C++, я получил три разных результата:
쐽
первый компилятор сигнализировал о повторном объявлении переменной
recordCount во втором цикле for и сгенерировал ошибку;
쐽
второй компилятор допустил объявление переменной
recordCount во втором цикле
for, но разрешил ее использование вне первого цикла for;
쐽
третий компилятор разрешил оба объявления переменных
recordCount и не допустил использования ни одной из них за пределами циклов, где они объяв- лялись.
Как это часто бывает с наиболее эзотерическими свойствами языка, реализации компиляторов могут различаться.
Насколько длинным может быть цикл?
Длина цикла может измеряться в строках кода или глубине вложенности.
Делайте циклы достаточно короткими, чтобы их можно было увидеть
сразу целиком Если вы обычно смотрите на циклы на вашем мониторе, а ваш монитор показывает 50 строк, то установите 50-строчное ограничение длины.
Эксперты предложили ограничивать длину цикла одной страницей. Однако ког- да вы оцените преимущество создания простого кода, вы редко будете писать циклы длиннее 15 или 20 строк.
Ограничивайте вложенность тремя уровнями Иссле- дования показали, что способность программистов разоб- раться в цикле существенно снижается, если уровень вло- женности превышает три уровня (Yourdon, 1986a). Если вам нужно большее чис- ло уровней, сделайте цикл короче (концептуально), вынеся его часть в отдельный метод или упростив управляющую структуру.
Выделяйте внутреннюю часть длинных циклов в отдельные методы Ес- ли цикл хорошо спроектирован, то код внутри него часто можно выделить в один или несколько методов, которые будут вызываться из цикла.
Делайте длинные циклы особенно ясными Длина увеличивает сложность. Если вы пишете короткий цикл, вы можете использовать более рискованные управля- ющие структуры, такие как
break и continue, множественные выходы, сложные условия завершения и т.д. Если вы пишете более длинный цикл и проявляете хоть какую-то заботу о читателях, вы предусмотрите в цикле только один выход и сде- лаете условие выхода исключительно понятным.
Перекрестная ссылка Об упроще- нии вложенности см. раздел 19.4.
378
ЧАСТЬ IV Операторы
16.3. Простое создание цикла — изнутри наружу
Если у вас иногда возникают затруднения при кодировании сложного цикла (что бывает у большинства программистов), есть простой способ реализовать его с первого раза. Вот как это сделать. Начните с одного действия. Закодируйте его с помощью констант. Затем сделайте отступ, окружите его циклом и замените кон- станты индексами цикла или вычисляемыми выражениями. Добавьте еще один цикл,
если он нужен, и замените другие константы. Повторите процесс нужное число раз. После этого добавьте код инициализации. Так как вы начали с одного дей- ствия и двигались в сторону его обобщения, то можете рассматривать этот про- цесс как кодирование изнутри наружу.
Допустим, вы разрабатываете программу для страховой компании. Ставки для страхования жизни варьируются в зависимости от возраста и пола страхователя. Ваша задача
— написать метод, вычисляющий общую страховую премию для группы лиц. Вам нужен цикл, который будет брать ставку для каждого челове- ка из списка и добавлять ее к общей сумме. Вот что нужно сделать.
Во-первых, в комментариях напишите шаги, которые должно выполнять тело цикла.
Легче записать, что необходимо сделать, когда вы не думаете о деталях синтакси- са, индексах цикла, массива и т. п.
Шаг 1: Создание цикла изнутри наружу (псевдокод)
— Получить ставку из таблицы.
— Добавить ставку к общей сумме.
Во-вторых, замените комментарии в теле цикла на код, насколько это возможно без фактического написания всего цикла. В данном случае возьмите ставку для одного лица и добавьте ее к сумме. Используйте реальные данные, а не абстракции.
Шаг 2: Создание цикла изнутри наружу (псевдокод)
table еще не использует индексов.
rate = table[ ]
totalRate = totalRate + rate
Пример предполагает, что
table — это массив, содержащий данные о ставках.
Сначала вам не надо беспокоиться об индексах массива.
rate — это переменная, в которой хранится ставка, выбранная из таблицы ставок. Соответственно
totalRate
— переменная, содержащая сумму всех ставок.
Далее добавьте индексы к массиву
table:
Шаг 3: Создание цикла изнутри наружу (псевдокод)
rate = table[ census.Age ][ census.Gender ]
totalRate = totalRate + rate
Доступ к элементам массива осуществляется в зависимости от возраста и пола,
поэтому
census.Age и census.Gender служат для индексации массива. Пример пред-
>
Перекрестная ссылка Кодирова- ние цикла изнутри наружу похо- же на ППП (см. главу 9).
ГЛАВА 16 Циклы
379
полагает, что
census — это структура, содержащая сведения о людях из рассчиты- ваемой группы.
Следующий шаг — построение цикла вокруг существующих выражений. Поскольку цикл должен вычислять ставки для каждого человека из группы, индекс должен перечислять всех членов группы.
Шаг 4: Создание цикла изнутри наружу (псевдокод)
For person = firstPerson to lastPerson rate = table[ census.Age, census.Gender ]
totalRate = totalRate + rate
End For
Все, что вы должны сделать, — это поместить цикл
for вокруг существующего кода и добавить к нему пару
begin-end. Напоследок убедитесь, что переменные, исполь- зующие индекс цикла
person, написаны правильно. В данном случае переменная
census изменяется вместе с person, поэтому ее следует корректно проиндексировать.
Шаг 5: Создание цикла изнутри наружу (псевдокод)
For person = firstPerson to lastPerson rate = table[ census[ person ].Age, census[ person ].Gender ]
totalRate = totalRate + rate
End For
И, наконец, напишите необходимую инициализацию. В этом примере нужно ини- циализировать переменную
totalRate.
Последний шаг: Создание цикла изнутри наружу (псевдокод)
totalRate = 0
For person = firstPerson to lastPerson rate = table[ census[ person ].Age, census[ person ].Gender ]
totalRate = totalRate + rate
End For
Если вы хотите добавить еще один цикл вокруг цикла
person, продолжайте таким же образом. Вы не должны жестко придерживаться этого порядка. Идея в том, чтобы начать с чего-то определенного, думать только об одной задаче в каждый момент времени и строить цикл из простых компонентов. Предпринимайте маленькие,
понятные шаги, постепенно обобщая и усложняя цикл. Таким образом, вы мини- мизируете количество кода, на котором необходимо одновременно сосредоточи- ваться и, следовательно, уменьшите вероятность ошибки.
16.4. Соответствие между циклами и массивами
Циклы и массивы часто связаны друг с другом. Зачастую цикл создается для манипуляций с массивами, и счетчики цикла один к одному соответствуют индексам массива. Так, следу- ющие индексы циклов
for соответствуют индексам массива:
Перекрестная ссылка О соответ- ствии между циклами и масси- вами см. также раздел 10.7.
380
ЧАСТЬ IV Операторы
Пример умножения массивов (Java)
for ( int row = 0; row < maxRows; row++ ) {
for ( int column = 0; column < maxCols; column++ ) {
product[ row ][ column ] = a[ row ][ column ] * b[ row ][ column ];
}
}
В языке Java цикл для таких операций с массивами необходим. Но стоит заметить,
что циклические структуры и массивы не обязательно должны использоваться вместе. Некоторые языки, особенно APL и Fortran 90 и более поздние, предостав- ляют операции с массивами, исключающие необходимость применять такие циклы,
как только что продемонстрированные. Вот так выглядит фрагмент кода на APL,
выполняющий ту же операцию:
Пример умножения массивов (APL)
product <- a x b
Вариант на APL проще и менее подвержен ошибкам. Он использует только три операнда, тогда как фрагмент на Java — 17. Он не содержит переменных цикла,
индексов массива или управляющих структур, которые можно некорректно зако- дировать.
Из этих примеров следует, что частично программирование направлено на ре- шение задачи, а частично — на решение этой задачи на определенном языке.
Выбранный вами язык существенно влияет на получаемый результат.
Контрольный список: циклы
Выбор и создание цикла
Используется ли цикл while вместо цикла for, если он больше подходит?
Создавался ли цикл изнутри наружу?
Вход в цикл
Выполняется ли вход в цикл сверху?
Расположен ли код инициализации непосредственно перед циклом?
Если необходим бесконечный или событийный цикл, конструируется ли он явно, или сделан такой ляп, как for i = 1 to 9999?
В цикле for в C++, C или Java резервируется ли заголовок цикла только для управляющего кода?
Тело цикла
Использует ли цикл скобки { и } или их эквиваленты для обрамления тела цикла и предотвращения проблем, связанных с неправильной модификацией?
Содержит ли тело цикла хоть что-то? Не пустое ли оно?
Сгруппированы ли служебные операции в начале или конце цикла?
Выполняет ли цикл одну и только одну функцию, как это делает хорошо спроектированный метод?
Достаточно ли цикл короткий, чтобы его можно было сразу увидеть целиком?
Не превышает ли вложенность цикла трех уровней?
http://cc2e.com/1616
1 ... 43 44 45 46 47 48 49 50 ... 104
ГЛАВА 16 Циклы
381
Вынесено ли содержимое длинного цикла в отдельный метод?
Если цикл достаточно длинный, написан ли он особенно ясно?
Индексы цикла
Если это цикл for, не выполняются ли манипуляции с индексом цикла внут- ри самого цикла?
Используется ли для сохранения важных значений индекса цикла вне этого цикла специальная переменная, а не сам индекс цикла?
Является ли индекс цикла порядковым или перечислимым типом, но не типом с плавающей запятой?
Имеет ли индекс цикла смысловое имя?
Не содержит ли цикл пересечения индексов?
Завершение цикла
Завершается ли цикл при всех возможных условиях?
Использует ли цикл счетчики безопасности, если они приняты на уровне стандарта?
Очевидно ли условие завершения цикла?
Если используются операторы break или continue, корректны ли они?
Ключевые моменты
쐽
Циклы сложны для понимания. Сохраняя их простыми, вы помогаете читате- лям вашего кода.
쐽
К способам упрощения циклов относятся: избегание экзотических видов цик- лов, минимизация вложенности, создание очевидных входов и выходов цикла и хранение служебного кода в одном месте.
쐽
Индексы цикла часто употребляются неправильно. Называйте их понятно и используйте только с одной целью.
쐽
Аккуратно продумайте весь цикл, чтобы убедиться, что он работает правиль- но во всех случаях и завершается при любых возможных обстоятельствах.
382
ЧАСТЬ IV Операторы
Г Л А В А 1 7
Нестандартные
управляющие структуры
Содержание
쐽
17.1. Множественные возвраты из метода
쐽
17.2. Рекурсия
쐽
17.3. Оператор
goto
쐽
17.4. Перспективы нестандартных управляющих структур
Связанные темы
쐽
Общие вопросы управления: глава 19
쐽
Последовательный код: глава 14
쐽
Код с условными операторами: глава 15
쐽
Код с циклами: глава 16
쐽
Обработка исключений: раздел 8.4
Несколько управляющих структур существует в сумрачной зоне между передовым краем технологии и полной дискредитацией и несостоятельностью, и часто в одно и то же время! Эти конструкции доступны не во всех языках, но там, где они есть,
они могут быть полезны при аккуратном применении.
17.1. Множественные возвраты из метода
Большинство языков поддерживает некоторые способы возврата управления после частичного выполнения метода. Операторы
return и exit — управляющие струк- туры, которые позволяют программе при желании завершить работу метода.
В результате функция завершается через нормальный канал выхода, возвращая уп- равление вызывающему методу. Слово
return здесь используется как общий тер- мин, обозначающий
return в C++ и Java, Exit Sub и Exit Function в Visual Basic и аналогичные конструкции. Далее перечислены некоторые принципы использо- вания оператора
return.
http://cc2e.com/1778
ГЛАВА 17 Нестандартные управляющие структуры
383
Используйте return, если это повышает читабельность В неко- торых методах при получении ответа хочется сразу вернуть управление вызывающей стороне. Если метод определен так, что обнаружение ошибки не требует никакой дополнительной очистки ресурсов, то отсутствие немедлен- ного возврата означает необходимость писать лишний код.
Вот хороший пример ситуации, когда возврат из нескольких частей метода име- ет смысл:
Пример правильного множественного возврата из метода (C++)
Этот метод возвращает перечислимый тип Comparison.
Comparison Compare( int value1, int value2 ) {
if ( value1 < value2 ) {
return Comparison_LessThan;
}
else if ( value1 > value2 ) {
return Comparison_GreaterThan;
}
return Comparison_Equal;
}
Другие примеры не настолько однозначны, что будет проиллюстрировано ниже.
Упрощайте сложную обработку ошибок с помощью сторожевых опера-
торов (досрочных return или exit) Если программа вынуждена проверять боль- шое количество ошибочных ситуаций перед выполнением номинальных действий,
это может привести к коду очень большой вложенности и замаскировать номи- нальный вариант. Вот пример такого кода:
Код, скрывающий номинальный вариант (Visual Basic)
If file.validName() Then
If file.Open() Then
If encryptionKey.valid() Then
If file.Decrypt( encryptionKey ) Then
Здесь код номинального варианта.
‘ Много кода.
End If
End If
End If
End If
Отступ основного кода метода внутри четырех условий
if выглядит неэстетично,
особенно если этот код в самом внутреннем блоке
if состоит из множества строк.
В таких случаях часто можно упростить логику, если все ошибочные ситуации проверять сначала, расчистив дорогу для номинального хода алгоритма. Вот как это может выглядеть:
>
>
384
ЧАСТЬ IV Операторы
Простой код, использующий сторожевые операторы
для прояснения номинального варианта (Visual Basic)
‘ Выполняем инициализацию. При обнаружении ошибок завершаем работу.
If Not file.validName() Then Exit Sub
If Not file.Open() Then Exit Sub
If Not encryptionKey.valid() Then Exit Sub
If Not file.Decrypt( encryptionKey ) Then Exit Sub
’ Много кода.
В таком простом примере описанный способ выглядит аккуратным решением, но промышленный код при обнаружении ошибки часто требует большего количе- ства служебных операций или действий по очистке ресурсов. Вот более реалис- тичный пример:
Более реалистичный код, использующий сторожевые операторы
для прояснения номинального варианта (Visual Basic)
‘ Выполняем инициализацию. При обнаружении ошибок завершаем работу.
If Not file.validName() Then errorStatus = FileError_InvalidFileName
Exit Sub
End If
If Not file.Open() Then errorStatus = FileError_CantOpenFile
Exit Sub
End If
If Not encryptionKey.valid() Then errorStatus = FileError_InvalidEncryptionKey
Exit Sub
End If
If Not file.Decrypt( encryptionKey ) Then errorStatus = FileError_CantDecryptFile
Exit Sub
End If
Здесь код номинального варианта.
‘ Много кода.
В коде промышленного масштаба использование
Exit Sub приводит к написанию довольно большого количества кода до обработки номинального варианта. Од- нако
Exit Sub позволяет избежать глубокой вложенности, присущей первому при- меру, и если код первого примера расширить с целью установки значений пере- менной
errorStatus, то вариант с Exit Sub покажется лучшим с точки зрения груп- пировки взаимосвязанных выражений. Когда вся пыль осядет, подход с
Exit Sub
покажется более удобным для чтения и сопровождения, и за небольшую цену.
>