Файл: Руководство по стилю программирования и конструированию по.pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 30.11.2023
Просмотров: 850
Скачиваний: 2
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
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
ГЛАВА 10 Общие принципы использования переменных
245
10.5. Персистентность
«Персистентность» — это еще одно слово, характеризующее длительность суще- ствования данных. Персистентность принимает несколько форм. Некоторые пе- ременные «живут»:
쐽
пока выполняется конкретный блок кода или метод: например, это перемен- ные, объявленные внутри цикла
for языка C++ или Java;
쐽
столько, сколько вы им позволяете: в Java переменные, созданные при помо- щи оператора
new, «живут» до сборки мусора; в C++ созданные аналогично переменные существуют, пока не будут уничтожены с помощью оператора
delete;
쐽
до завершения программы: этому описанию соответствуют глобальные пере- менные в большинстве языков, а также статические переменные в языках C++
и Java;
쐽
всегда: такими переменными могут быть значения, которые вы храните в БД
между запусками программы, — так, например, в случае интерактивной про- граммы, позволяющей пользователям настраивать цвета экрана, вы могли бы хранить эти цвета в файле, считывая их при каждой загрузке программы.
Главная проблема персистентности возникает, когда вы предполагаете, что пере- менная существует дольше, чем есть на самом деле. Переменная похожа на пакет молока в холодильнике. Предполагается, что он может храниться неделю. Иног- да он хранится месяц, и с молоком ничего не происходит, а иногда молоко ски- сает через пять дней. Переменная может быть столь же непредсказуема. Если вы попытаетесь использовать значение переменной после окончания нормальной дли- тельности ее существования, получите ли вы ее прежнее значение? Иногда зна- чение переменной «скисает», и вы получаете ошибку. В других случаях компиля- тор оставляет старое значение неизменным, позволяя вам воображать, что все нормально.
Проблем подобного рода можно избежать.
쐽
Включайте в программу отладочный код или утвержде- ния для проверки важных переменных на предмет до- пустимости их значений. Если значения недопустимы,
отображайте предупреждение, рекомендующее присту- пить к поиску кода неверной инициализации.
쐽
Завершив работу с переменными, присваивайте им «не- допустимые значения». Скажем, после освобождения памяти при помощи опе- ратора