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

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


Скачать 7.6 Mb.
НазваниеРуководство по стилю программирования и конструированию по
Дата18.05.2023
Размер7.6 Mb.
Формат файлаpdf
Имя файлаCode_Complete.pdf
ТипРуководство
#1139697
страница47 из 104
1   ...   43   44   45   46   47   48   49   50   ...   104
ГЛАВА 16 Циклы
373
Используйте continue для проверок в начале цикла Хорошим применени- ем оператора
continue будет перемещение операций в конец тела цикла после про- верки некоторого условия в его начале. Например, если цикл читает записи, от- брасывает часть из них, а остальные обрабатывает, вы можете поместить подоб- ную проверку в начало цикла:
Пример относительно безопасного использования continue (псевдокод)
while ( not eof( file ) ) do read( record, file )
if ( record.Type <> targetType ) then continue
— Обрабатываем запись targetType.
end while
Такое использование
continue позволяет избегать проверок if, что эффективно уменьшит отступы внутри всего тела цикла. С другой стороны, если
continue воз- никает в середине или конце цикла, используйте вместо него
if.
Используйте структуру break с метками, если ваш язык ее поддержи-
вает Java поддерживает помеченные операторы break, что позволяет предотв- ратить проблемы, приведшие к выходу из строя телефонов в Нью-Йорке.
break с меткой можно использовать для выхода из цикла
for, условия if или любого блока кода, заключенного в скобки (Arnold, Gosling and Holmes, 2000).
Вот возможное решение «нью-йоркской проблемы», переписанное на Java вмес- то C++, что позволяет использовать
break с меткой:
Пример лучшего использования помеченного оператора break
в блоке do-switch-if (Java)
do {
switch
CALL_CENTER_DOWN:
if () {
Назначение помеченного break однозначно.
break CALL_CENTER_DOWN;
}
} while ( ... );
Используйте операторы break и continue очень осторожно Применение
break исключает возможность представления цикла в виде черного ящика. Если вы ограничиваетесь только одним выражением для управления условием выхода из цикла, то получаете мощное средство для упрощения циклов. Применение
break
>

374
ЧАСТЬ IV Операторы заставляет читателя смотреть внутрь цикла, чтобы разобраться в его управлении.
Это усложняет понимание цикла.
Используйте
break только после того, как рассмотрели все альтернативы. Вы не можете сказать с уверенностью, хороши или плохи конструкции
continue и break.
Некоторые ученые утверждают, что это допустимые технологии в структурном программировании, а некоторые — что нет. Поскольку вы не знаете, правильно ли применять
continue и break вообще, используйте их, но не забывайте, что вы можете быть неправы. На самом деле это сводится к простому утверждению: если вы не можете аргументировать применение
break или continue, не применяя их.
Проверка граничных точек
При разработке цикла обычно представляют интерес три точки: первая итерация,
случайно выбранная итерация в середине и последняя итерация. Когда вы созда- ете цикл, мысленно пройдитесь по этим трем точкам и убедитесь, что в цикле нет ошибки потери единицы. Если цикл содержит какие-то специальные случаи, вы- полнение которых отличается от первой или последней итерации, проверьте их тоже. Если цикл производит сложные вычисления, достаньте свой калькулятор и проверьте их вручную.
Готовность выполнять такой вид проверки — ключевое различие между квалифицированными и неквалифицированными программистами. Пер- вые проделывают мысленное моделирование и вычисления вручную,
потому что знают, что эти меры помогут им найти ошибки.
Вторые имеют склонность к случайному экспериментированию, пока не найдут правдоподобную комбинацию. Если цикл не работает так, как предполагалось,
неумелый программист меняет знак
< на <=. Если и это не помогает, он исправ- ляет индекс цикла, добавляя или вычитая 1. В конечном счете таким способом программист может нащупать правильную комбинацию или просто заменить изначальную ошибку более незаметной. Даже если этот случайный процесс при- ведет к правильной программе, программист не будет знать, почему она работа- ет корректно.
Мысленное моделирование и ручные вычисления могут дать несколько преиму- ществ. Умственная тренировка приводит к меньшему количеству ошибок при первоначальном кодировании, более быстрому обнаружению проблем при отладке и в целом более полному пониманию программы. Умственные упражнения озна- чают, что вы знаете, как работает код, а не просто предполагаете это.
Использование переменных цикла
Далее описаны некоторые принципы применения переменных цикла.
Используйте порядковые или перечислимые типы для
границ массивов и циклов Обычно счетчики циклов должны быть целыми значениями. Числа с плавающей за- пятой плохо инкрементируются. Например, вы можете при- бавить 1,0 к 26 742 897,0 и получить 26 742 897,0 вместо
26 742 898,0. Если это число используется как индекс цикла, вы получите беско- нечный цикл.
Перекрестная ссылка Об имено- вании переменных цикла см.
подраздел «Именование индек- сов циклов» раздела 11.2.

ГЛАВА 16 Циклы
375
Используйте смысловые имена переменных, чтобы сделать вло-
женные циклы читабельными Массивы часто индексируются с по- мощью тех же переменных, что используются как индексы цикла. Если у вас одномерный массив, то вы еще сможете выйти сухим их воды, применяя
i, j
или
k для его индексации. Но если у массива два и более измерений, вам следует задавать значимые имена для индексов, чтобы прояснить свои действия. Смысло- вые имена индексов массивов одновременно уточняют и назначение цикла, и эле- мент массива, к которому вы планируете обратиться.
Вот пример кода, который не применяет этот принцип: в нем использованы бес- смысленные имена
i, j и k:
Пример неправильных имен
переменных цикла (Java)
for ( int i = 0; i < numPayCodes; i++ ) {
for ( int j = 0; j < 12; j++ ) {
for ( int k = 0; k < numDivisions; k++ ) {
sum = sum + transaction[ j ][ i ][ k ];
}
}
}
Как вы думаете, что означают индексы в элементе
transaction? Сообщают ли пе- ременные
i, j и k что-либо о содержимом transaction? Если вы знаете объявление
transaction, можете ли вы легко определить, указаны ли индексы в правильном порядке? Вот тот же цикл с более читабельными именами переменных:
Пример хороших имен переменных цикла на Java
for ( int payCodeIdx = 0; payCodeIdx < numPayCodes; payCodeIdx++ ) {
for (int month = 0; month < 12; month++ ) {
for ( int divisionIdx = 0; divisionIdx < numDivisions; divisionIdx++ ) {
sum = sum + transaction[ month ][ payCodeIdx ][ divisionIdx ];
}
}
}
Как вы думаете, что означают индексы в элементе
transaction на этот раз? В этом случае ответ получить проще, потому что имена переменных
payCodeIdx, month
и
divisionIdx гораздо красноречивее, чем i, j и k. Компьютер с одинаковой легко- стью прочитает обе версии цикла. Однако людям легче будет читать вторую вер- сию, чем первую, поэтому второй вариант лучше, поскольку ваша основная ауди- тория состоит из людей, а не из компьютеров.
Используйте смысловые имена во избежание пересечения индексов При- вычное использование переменных
i, j и k приводит к увеличению риска пересе- чения индексов — использованию одного и того же имени индекса для разных целей. Взгляните:

376
ЧАСТЬ IV Операторы
Пример пересечения индексов (C++)
i сначала используется здесь...
for ( i = 0; i < numPayCodes; i++ ) {
// много кода for ( j = 0; j < 12; j++ ) {
// много кода
...а теперь здесь for ( i = 0; i < numDivisions; i++ ) {
sum = sum + transaction[ j ][ i ][ k ];
}
}
}
Применение
i настолько привычно, что эта переменная используется в одной вложенной структуре дважды. Второй цикл
for, управляемый i, конфликтует с пер- вым — это и есть пересечение индексов. Применение более значимых имен, чем
i, j и k, предотвратило бы проблему. Вообще, если тело цикла содержит больше пары строк кода, или может вырасти, или входит в группу вложенных циклов, из- бегайте переменных
i, j и k.
Ограничивайте видимость переменных-индексов цикла самим циклом Пе- ресечение индексов цикла и другое применение индексов вне самих циклов —
настолько важная проблема, что разработчики языка Ada решили сделать индек- сы цикла
for недоступными вне цикла. Попытка использования переменной-ин- декса вне цикла
for приводит к ошибке времени компиляции.
C++ и Java в какой-то мере реализуют ту же идею — они позволяют объявлять индексы цикла в нем самом, но не требуют этого. Выше, в примере раздела «Из- бегайте писать код, зависящий от последнего значения индекса цикла», перемен- ная
recordCount может быть объявлена внутри выражения for, что ограничит ее область видимости этим циклом:
Пример объявления переменной-индекса цикла внутри цикла for (C++)
for ( int recordCount = 0; recordCount < MAX_RECORDS; recordCount++ ) {
// Циклический код, использующий recordCount.
}
В принципе эта методика должна позволять создавать код, повторно объявляю- щий переменную
recordCount в нескольких циклах без риска неправильного ис- пользования двух разных
recordCount. Такое применение позволило бы писать,
например, такой код:
Пример объявления переменных-индексов внутри циклов for
и их (возможно!) безопасное повторное использование (C++)
for ( int recordCount = 0; recordCount < MAX_RECORDS; recordCount++ ) {
// Циклический код, использующий recordCount.
}
>
>

ГЛАВА 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


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