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

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


Скачать 7.6 Mb.
НазваниеРуководство по стилю программирования и конструированию по
Дата18.05.2023
Размер7.6 Mb.
Формат файлаpdf
Имя файлаCode_Complete.pdf
ТипРуководство
#1139697
страница31 из 104
1   ...   27   28   29   30   31   32   33   34   ...   104
ГЛАВА 10 Общие принципы использования переменных
237
Настройте компилятор так, чтобы он автоматически инициализиро-
вал все переменные Если ваш компилятор поддерживает такую возможность,
заставьте его автоматически инициализировать все переменные. Однако, полагаясь на компилятор, вы можете столкнуться с проблемами при переносе кода на другой компьютер или при использовании другого компилятора. Документируйте исполь- зование параметров компилятора — без такой документации предположения, осно- ванные на конкретных параметрах компилятора, определить очень трудно.
Внимательно изучайте предупреждения компилятора Многие компиля- торы предупреждают об использовании неинициализированных переменных.
Проверяйте корректность входных параметров
Это еще один эффективный способ предотвращения ошибок инициализации. Прежде чем присвоить входные значения чему-либо, убедитесь, что они допустимы.
Используйте утилиту проверки доступа к памяти для обнаружения не-
верно инициализированных указателей Некоторые ОС сами следят за кор- ректностью обращений к памяти, выполняемых при помощи указателей, другие ос- тавляют вас на произвол судьбы. Тогда можно приобрести инструмент проверки доступа к памяти и проконтролировать использование указателей в своей программе.
Инициализируйте рабочую память при запуске программы Инициализа- ция рабочей памяти известным значением облегчает поиск ошибок инициализа- ции. Этого позволяют достичь описанные ниже подходы.

Вы можете использовать специализированную утилиту для заполнения памя- ти определенным значением перед запуском программы. Для некоторых це- лей хорошо подходит значение 0, потому что оно гарантирует, что неиници- ализированные указатели будут указывать на нижнюю область памяти, благо- даря чему их будет относительно легко найти. В случае процессоров с архи- тектурой Intel целесообразно заполнить память значением 0xCC, потому что оно соответствует машинному коду команды точки прерывания; если вы запу- стите код в отладчике и попытаетесь выполнить данные, а не код, вы потонете в точках прерывания. Еще одно достоинство значения 0xCC в том, что его легко заметить в дампах памяти; кроме того, оно редко используется. По этим же причинам Брайан Керниган и Роб Пайк предлагают заполнять память констан- той 0xDEADBEEF
1
(Kernighan and Pike, 1999).

Если вы применяете утилиту заполнения памяти, можете время от времени изменять используемое ей значение. «Встряхивание» программы иногда позво- ляет обнаружить проблемы, которые остаются скрытыми, если среда никогда не изменяется.

Вы можете сделать так, чтобы программа инициализировала свою рабочую память при запуске. Цель заполнения памяти до запуска программы — обна- ружение дефектов, тогда как цель этого подхода — их сокрытие. Заполняя рабочую память каждый раз одинаковым значением, вы сможете гарантиро- вать, что программа не будет зависеть от случайных изменений начальной конфигурации памяти.
Перекрестная ссылка О проверке входных параметров см. главу 8,
преимущественно раздел 8.1.
1
Букв. «мертвая корова». —
Прим. перев.

238
ЧАСТЬ III Переменные
10.4. Область видимости
Область видимости можно понимать как «известность» переменной в програм- ме. Областью видимости называют фрагмент программы, в котором переменная известна и может быть использована. Переменная с ограниченной или неболь- шой областью видимости известна только в небольшом фрагменте программы:
в качестве примера можно привести индекс, используемый в теле одного неболь- шого цикла. Переменная с большой областью видимости известна во многих местах программы: примером может служить таблица с данными о сотрудниках, исполь- зуемая по всей программе.
В разных языках реализованы разные подходы к области видимости. В некото- рых примитивных языках все переменные глобальны. В этом случае вы не имее- те контроля над областью видимости переменных, что создает много проблем.
В C++ и похожих языках переменная может иметь область видимости, соответ- ствующую блоку (фрагменту кода, заключенному в фигурные скобки), методу, классу
(возможно, и производным от него классам) или всей программе. В Java и C#
переменная может также иметь область видимости, соответствующую пакету или пространству имен (набору классов).
Ниже я привел ряд советов, относящихся к области видимости.
Локализуйте обращения к переменным
Код, расположенный между обращениями к переменной, является «окном уязви- мости». Чем больше это окно, тем выше вероятность, что в его пределах будет добавлен новый код, искажающий значение переменной, и тем труднее следить за значением переменной при чтении кода. Поэтому обращения к переменной всегда целесообразно локализовать, группируя их вместе.
Идея локализации обращений к переменным самоочевидна, однако она допуска- ет и формальную оценку. Одним из методов оценки степени сгруппированности обращений к переменной является определение «интервала» (span) между обра- щениями, например:
Пример определения интервалов между обращениями
к переменным (Java)
a = 0;
b = 0;
c = 0;
a = b + c;
В данном случае между первым и вторым обращениями к
a находятся две строки кода, поэтому и интервал равен 2. Между двумя обращениями к
b — одна строка,
что дает нам интервал, равный 1, ну а интервал между обращениями к
c равен 0.
Вот еще один пример:

ГЛАВА 10 Общие принципы использования переменных
239
Пример интервалов, равных 1 и 0 (Java)
a = 0;
b = 0;
c = 0;
b = a + 1;
b = b / c;
В этом примере между первым и вторым обращениями к
b
одна строка кода, а между вторым и третьим обращениями строк нет, поэтому интервалы равны соответственно 1 и 0.
Средний интервал вычисляется путем усреднения отдельных интервалов. Так, во втором примере средний интервал между обращениями к
b равен (1+0)/2, или 0,5. Локализовав обра- щения к переменным, вы позволите программисту, который будет читать ваш код,
сосредоточиваться на меньшем фрагменте программы в каждый конкретный мо- мент времени. Если обращения будут распределены по большему фрагменту кода,
уследить за ними будет сложнее. Таким образом, главное преимущество локали- зации обращений к переменным в том, что оно облегчает чтение программы.
Делайте время жизни переменных как можно короче
С интервалом между обращениями к переменной тесно связано «время жизни»
переменной — общее число строк, на протяжении которых переменная исполь- зуется. Жизнь переменной начинается при первом обращении к ней, а заканчи- вается при последнем.
В отличие от интервала время жизни переменной не зависит от числа обраще- ний к ней между первым и последним обращениями. Если переменная в первый раз вызывается в строке 1, а в последний — в строке 25, ее время жизни равно 25
строкам. Если переменная используется только в этих двух строках, средний ин- тервал между обращениями к ней — 23 строки. Если бы между строками 1 и 25
переменная вызывалась в каждой строке, она имела бы средний интервал, равный
0, но время ее жизни по-прежнему равнялось бы 25 строкам. Связь интервалов меж- ду обращениями к переменной и времени ее жизни пояснена на рис. 10-1.
Как и интервал между обращениями к переменной, время ее жизни желательно делать как можно короче. Преимущество в обоих случаях одинаково: это умень- шает окно уязвимости, снижая вероятность неверного или неумышленного изме- нения переменной между действительно нужными обращениями к ней.
Второе преимущество короткого срока жизни: оно позволяет получить верное представление о коде. Если переменная изменяется в строке 10 и вызывается в строке 45, само пространство между двумя обращениями подразумевает, что пе- ременная используется также между строками 10 и 45. Если переменная изменя- ется в строке 44 и вызывается в строке 45, других обращений к ней между этими строками быть не может, что позволяет вам сосредоточиться на меньшем фраг- менте кода.
Дополнительные сведения Об интервалах между обращения- ми к переменным см. работу
«Software Engineering Metrics and Models» (Conte, Dunsmore,
and Shen, 1986).

240
ЧАСТЬ III Переменные
Рис. 10-1. «Длительное время жизни» подразумевает, что переменная используется
в крупном фрагменте кода. При «коротком времени жизни» переменная используется
лишь в небольшом фрагменте. «Интервал между обращениями» к переменной
характеризует, насколько тесно сгруппированы обращения к переменной
Короткое время жизни снижает вероятность ошибок инициализации. По мере изменения программы линейные участки кода имеют тенденцию превращаться в циклы, при этом программисты часто забывают про инициализацию перемен- ных, выполненную вдали от цикла. Поддерживая код инициализации и код цикла в непосредственной близости, вы снизите вероятность того, что изменения при- ведут к ошибкам инициализации.
Кроме того, короткое время жизни облегчает чтение кода. Чем меньше строк кода нужно удерживать в уме в каждый конкретный момент времени, тем проще по- нять код. К тому же при небольшом времени жизни на экране помещаются сразу все обращения к переменной, что облегчает редактирование и отладку.
Наконец, короткое время жизни облегчает разделение крупного метода на мень- шие. Если обращения к переменным сгруппированы в небольшом фрагменте, его проще выделить в отдельный метод.
Оценка времени жизни переменной
Время жизни переменной можно формализовать, подсчитав число строк между пер- вым и последним обращениями к ней (с учетом первой и последней строк). В следу- ющем примере каждая из переменных обладает слишком долгим временем жизни:
Пример слишком долгого времени жизни переменных (Java)
1 // инициализация каждой переменной
2 recordIndex = 0;
3 total = 0;

ГЛАВА 10 Общие принципы использования переменных
241 4 done = false;
26 while ( recordIndex < recordCount ) {
27 ...
Последнее обращение к переменной recordIndex.
28 recordIndex = recordIndex + 1;
64 while ( !done ) {
Последнее обращение к переменной total.
69 if ( total > projectedTotal ) {
Последнее обращение к переменной done.
70 done = true;
Времена жизни переменных:
recordIndex
(строка 28 - строка 2 + 1) = 27
total (строка 69 - строка 3 + 1) = 67
done
(строка 70 - строка 4 + 1) = 67
Среднее время жизни (27 + 67 + 67) / 3 »54
Следующий пример аналогичен предыдущему, только теперь обращения к пере- менным сгруппированы более тесно:
Пример хорошего, короткого времени жизни переменных (Java)
Инициализация переменной recordIndex ранее выполнялась в строке 3.
25 recordIndex = 0;
26 while ( recordIndex < recordCount ) {
27 ...
28 recordIndex = recordIndex + 1;
Инициализация переменных total и done ранее выполнялась в строках 4 и 5.
62 total = 0;
63 done = false;
64 while ( !done ) {
69 if ( total > projectedTotal ) {
70 done = true;
Теперь времена жизни переменных равны:
recordIndex
(строка 28 - строка 25 + 1) = 4
total (строка 69 - строка 62 + 1) = 8
done
(строка 70 - строка 63 + 1) = 8
Среднее время жизни (4 + 8 + 8) / 3 »7
>
>
>
>
>

242
ЧАСТЬ III Переменные
Интуиция подсказывает, что второй вариант предпочтитель- нее, так как инициализация переменных выполняется бли- же к месту их использования. Сравнение среднего времени жизни переменных — 54 и 7 — подкрепляет этот интуитив- ный вывод конкретными цифрами.
Какое время жизни считать приемлемым? А что можно сказать об интервале? Конк- ретных цифр у нас пока нет, но разумно предположить, что и интервал между обра- щениями к переменной, и время ее жизни следует пытаться свести к минимуму.
Если в этом ключе проанализировать глобальные переменные, окажется, что им соответствует огромный средний интервал между обращениями и такое же вре- мя жизни, — это одна из многих обоснованных причин избегать глобальных пе- ременных.
Общие советы по минимизации области видимости
Ниже даны конкретные рекомендации по минимизации области видимости.
Инициализируйте переменные, используемые в цикле,
непосредственно перед циклом, а не в начале метода,
содержащего цикл Следование этому совету снизит ве- роятность того, что при изменении цикла вы забудете изме- нить инициализацию используемых в нем переменных. Если же цикл будет вложен в новый цикл, переменные будут инициализироваться при каждой итерации нового цикла, а не только при первой.
Не присваивайте переменной значение вплоть до его
использования Вероятно, вы знаете, насколько трудно бывает найти строку, в которой переменной было присво- ено ее значение. Чем больше вы сделаете для прояснения того, где переменная получает свое значение, тем лучше.
Такие языки, как C++ и Java, позволяют инициализировать переменные следующим образом:
Пример грамотного объявления и инициализации переменных (C++)
int receiptIndex = 0;
float dailyReceipts = TodaysReceipts();
double totalReceipts = TotalReceipts( dailyReceipts );
Группируйте связанные команды В следующих фраг- ментах на примере метода, суммирующего дневную выручку,
показано, как сгруппировать обращения к переменным, что- бы за ними было проще следить. В первом примере этот принцип нарушен:
Пример запутанного использования двух наборов переменных (C++)
void SummarizeData(...) {
Перекрестная ссылка Об иници- ализации переменных около места их использования см.
раздел 10.3.
Перекрестная ссылка Об этом стиле объявления и определе- ния переменных см. подраздел
«В идеальном случае сразу объявляйте и определяйте каж- дую переменную непосредствен- но перед первым обращением к ней» раздела 10.3.
Перекрестная ссылка О группи- ровке связанных команд см.
также раздел 14.2.
Дополнительные сведения О
времени жизни переменных см.
работу «Software Engineering Met- rics and Models» (Conte, Duns- more, and Shen, 1986).

ГЛАВА 10 Общие принципы использования переменных
243
Команды, в которых используются два набора переменных.
GetOldData( oldData, &numOldData );
GetNewData( newData, &numNewData );
totalOldData = Sum( oldData, numOldData );
totalNewData = Sum( newData, numNewData );
PrintOldDataSummary( oldData, totalOldData, numOldData );
PrintNewDataSummary( newData, totalNewData, numNewData );
SaveOldDataSummary( totalOldData, numOldData );
SaveNewDataSummary( totalNewData, numNewData );
}
Этот небольшой фрагмент заставляет следить сразу за шестью переменными:
oldData, newData, numOldData, numNewData, totalOldData и totalNewData. В следу- ющем примере благодаря разделению кода на два логических блока это число снижено до трех:
Пример более понятного использования двух наборов переменных (C++)
void SummarizeData( ... ) {
Команды, в которых используются «старые данные» (oldData).
GetOldData( oldData, &numOldData );
totalOldData = Sum( oldData, numOldData );
PrintOldDataSummary( oldData, totalOldData, numOldData );
SaveOldDataSummary( totalOldData, numOldData );
Команды, в которых используются «новые данные» (newData).
GetNewData( newData, &numNewData );
totalNewData = Sum( newData, numNewData );
PrintNewDataSummary( newData, totalNewData, numNewData );
SaveNewDataSummary( totalNewData, numNewData );
}
Каждый из двух блоков, полученных при разделении кода, короче, чем первона- чальный блок, и содержит меньше переменных. Такой код легче понять, а если вам придется разбить его на отдельные методы, меньшие блоки, содержащие мень- шее число переменных, позволят выполнять эту задачу эффективнее.
Разбивайте группы связанных команд на отдельные методы При прочих равных условиях переменная из более короткого метода обычно характеризуется меньшим интервалом между обращениями и меньшим временем жизни, чем переменная из более крупного метода. Разбиение группы связанных команд на отдельные методы позволяет уменьшить область видимости, которую может иметь переменная.
Начинайте с самой ограниченной области видимос-
ти и расширяйте ее только при необходимости Что- бы минимизировать область видимости переменной, поста- райтесь сделать ее как можно более локальной. Область ви-
Перекрестная ссылка О гло- бальных переменных см. раздел
13.3.
>
>
>

244
ЧАСТЬ III Переменные димости гораздо сложнее сжать, чем расширить — иначе говоря, превратить гло- бальную переменную в переменную класса сложнее, чем наоборот. Защищенные данные-члены класса также сложнее превратить в закрытые, чем закрытые в за- щищенные. Так что, если сомневаетесь, выбирайте наименьшую возможную об- ласть видимости переменной: попытайтесь сделать переменную локальной для от- дельного цикла, локальной для конкретного метода, затем — закрытой перемен- ной класса, затем — защищенной, далее попробуйте включить ее в пакет (если ваш язык программирования поддерживает пакеты) и лишь в крайнем случае сделай- те ее глобальной.
Комментарии по поводу минимизации области видимости
Подход к минимизации области видимости переменных часто зависит от точки зрения на вопросы «удобства» и «интеллектуальной управляемости». Некоторые программисты делают многие переменные глобальными для того, чтобы облег- чить доступ к ним и не беспокоиться о списках параметров и правилах области видимости. В их умах удобство доступа к глобальным переменным перевешивает связанную с этим опасность.
Другие предпочитают делать переменные как можно более локальными, потому что локальная область видимости спо- собствует интеллектуальной управляемости. Чем больше информации вы скрыли, тем меньше вам нужно удерживать в уме в каждый конкретный момент времени и тем ниже вероятность того, что вы допустите ошибку, забыв одну из многих деталей, о которых нужно было помнить.
Разница между философией «удобства» и философией «интеллектуаль- ной управляемости» сводится к различию между ориентацией на напи- сание программы и ориентацией на ее чтение. Максимизация области видимости может облегчить написание программы, но программу, в которой каж- дый метод может вызвать любую переменную в любой момент времени, сложнее понять, чем код, основанный на грамотно организованных методах. Сделав дан- ные глобальными, вы не сможете ограничиться пониманием работы одного ме- тода: вы должны будете понимать работу всех других методов, которые вместе с ним используют те же глобальные данные. Подобные программы сложно читать,
сложно отлаживать и сложно изменять.
Так что ограничивайте область видимости каждой перемен- ной минимальным фрагментом кода. Можете ограничить ее одним циклом или одним методом — великолепно! Не по- лучается — ограничьте область видимости методами одно- го класса. Если и это невозможно, создайте методы досту- па, позволяющие использовать переменную совместно с другими классами. «Голые» глобальные данные требуются редко, если вообще та- кое бывает.
Перекрестная ссылка Идея ми- нимизации области видимости связана с идеей сокрытия ин- формации [см. подраздел
«Скрывайте секреты (к вопро- су о сокрытии информации)»
раздела 5.3].
Перекрестная ссылка О методах доступа см. подраздел «Исполь- зование методов доступа вме- сто глобальных данных» разде- ла 13.3.

1   ...   27   28   29   30   31   32   33   34   ...   104


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