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

Совершенный код. Совершенный код. Мастер-класс. Стив Макконнелл. Руководство по стилю программирования и конструированию по


Скачать 5.88 Mb.
НазваниеРуководство по стилю программирования и конструированию по
АнкорСовершенный код
Дата31.03.2023
Размер5.88 Mb.
Формат файлаpdf
Имя файлаСовершенный код. Мастер-класс. Стив Макконнелл.pdf
ТипРуководство
#1028502
страница48 из 106
1   ...   44   45   46   47   48   49   50   51   ...   106
ГЛАВА 16 Циклы
371
// Много кода return( found );
Этот второй фрагмент использует дополнительную переменную и располагает обращения к
recordCount в более ограниченном пространстве. Как часто бывает при применении вспомогательной логической переменной, результирующий код становится яснее.
Рассмотрите использование счетчиков безопасности Счетчик безопасно#
сти — это переменная, увеличивающаяся при каждом проходе цикла, чтобы определить, не слишком ли много раз выполняется цикл. Если вы пишете програм#
му, в которой любая ошибка будет катастрофической, вы можете использовать счетчики безопасности, чтобы убедиться, что все циклы заканчиваются. Такой цикл на C++ вполне может использовать счетчик безопасности:
Пример цикла, который мог бы использовать счетчик безопасности (C++)
do {
node = node>Next;
} while ( node>Next != NULL );
Вот тот же код с добавленным счетчиком безопасности:
Пример использования счетчика безопасности (C++)
safetyCounter = 0;
do {
node = node>Next;
Здесь код счетчика безопасности.
safetyCounter++;
if ( safetyCounter >= SAFETY_LIMIT ) {
Assert( false, “Internal Error: SafetyCounter Violation.” );
}
} while ( node>Next != NULL );
Счетчики безопасности не панацея. Добавляемые в код по одному, они увеличи#
вают сложность и могут привести к дополнительным ошибкам. Так как они не при#
меняются в каждом цикле, вы можете забыть поддержать код счетчика при моди#
фикации циклов в той части программы, где они все же используются. Но если счетчики безопасности вводятся на уровне проектного стандарта для критичес#
ких циклов, вы будете ожидать их, и код этих счетчиков будет не более подвер#
жен ошибкам, чем любой другой.
Досрочное завершение цикла
Многие языки предоставляют средства для завершения цикла без выполнения условий
for или while. В данном обсуждении слово break обозначает общий тер#
>

372
ЧАСТЬ IV Операторы мин для оператора
break в C++, C и Java; выражений Exit%Do и Exit%For в Visual Basic и подобных конструкций, включая имитации с помощью
goto, в языках, не под#
держивающих
break напрямую. Оператор break (или его эквивалент) приводит к завершению цикла через нормальный канал выхода. Программа продолжает вы#
полнение с первого оператора, расположенного после цикла.
Оператор
continue похож на break в том смысле, что это вспомогательное сред#
ство для управления циклом. Однако вместо выхода из цикла,
continue заставляет программу пропустить тело цикла и продолжить выполнение со следующей ите#
рации. Оператор
continue — это сокращенный вариант блока if%then, предотвра#
щающего выполнение остальной части цикла.
Рассмотрите использование операторов break вместо логических фла'
гов в цикле while Порой добавление логических флагов в цикл while с целью имитации выхода из тела цикла усложняет чтение кода. Иногда вы можете убрать несколько уровней отступа в цикле и упростить его управление, просто исполь#
зуя
break вместо группы проверок if. Размещение нескольких отдельных условий
break рядом с кодом, приводящим к их выполнению, может уменьшить вложен#
ность и сделать цикл читабельнее.
Остерегайтесь цикла с множеством операторов break, разбросанных по
всему коду Цикл, содержащий большое количество операторов break, может сиг#
нализировать о нечетком представлении структуры цикла или его роли в окру#
жающем коде. Рост числа
break увеличивает вероятность, что цикл может быть более ясно представлен в виде набора нескольких циклов вместо одного цикла с мно#
жеством выходов.
Согласно статье в «Software Engineering Notes» программная ошибка, которая 15
января 1990 года на 9 часов вывела из строя телефонную сеть Нью#Йорка, воз#
никла благодаря лишнему оператору
break.(SEN, 1990):
Пример ошибочного использования оператора break в блоке do-switch-if (C++)
do {
switch if () {
Этот break предназначался для if, но вместо этого привел к выходу из switch.
break;
}
} while ( ... );
Большое количество
break не обязательно означает ошибку, но их присутствие в цикле — тревожный сигнал: как канарейка в шахте, задыхающаяся из#за недостатка воздуха, вместо того чтобы петь.
>

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

1   ...   44   45   46   47   48   49   50   51   ...   106


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