Файл: Руководство по стилю программирования и конструированию по.pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 30.11.2023
Просмотров: 845
Скачиваний: 2
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
234
ЧАСТЬ III Переменные заться область памяти, содержащая код. В этом блоке может находиться фрагмент
ОС. Проблема с указателями может проявляться совершенно неожиданным обра- зом, изменяющимся от случая к случаю, поэтому найти такие ошибки сложнее,
чем любые другие.
Ниже описаны способы предотвращения проблем, связанных с инициализацией.
Инициализируйте каждую переменную при ее объявлении Инициализация переменных при их объявлении — простая методика защитного программиро- вания и хорошая страховка от ошибок инициализации. Так, следующий код по- зволяет гарантировать, что инициализация массива
studentGrades будет выполняться при каждом вызове метода, содержащего массив:
Пример инициализации массива при его объявлении (C++)
float studentGrades[ MAX_STUDENTS ] = { 0.0 };
Инициализируйте каждую переменную там, где она
используется в первый раз Visual Basic и некоторые другие языки не позволяют инициализировать переменные при их объявлении. В результате код может принимать вид,
при котором сначала выполняется объявление нескольких переменных, а потом эти переменные инициализируются — и то и другое про- исходит вдали от места фактического использования переменных в первый раз:
Пример плохой инициализации
переменных (Visual Basic)
‘ объявление всех переменных
Dim accountIndex As Integer
Dim total As Double
Dim done As Boolean
’ инициализация всех переменных accountIndex = 0
total = 0.0
done = False
’ использование переменной accountIndex
’ использование переменной total
’ использование переменной done
While Not done
Лучше инициализировать каждую переменную как можно ближе к месту первого обращения к ней:
Перекрестная ссылка Проверка входных параметров является еще одной формой защитного программирования (см. главу 8).
ГЛАВА 10 Общие принципы использования переменных
235
Пример хорошей инициализации переменных (Visual Basic)
Dim accountIndex As Integer accountIndex = 0
’ использование переменной accountIndex
Dim total As Double
Переменная total объявляется и инициализируется непосредственно перед ее использованием.
total = 0.0
’ использование переменной total
Dim done As Boolean
Переменная done также объявляется и инициализируется непосредственно перед ее использова- нием.
done = False
’ использование переменной done
While Not done
Второй вариант лучше первого по нескольким причинам. Пока выполнение пер- вого примера дойдет до фрагмента, в котором используется переменная
done, она может оказаться измененной. Даже если при написании программы это не так, нельзя гарантировать, что этого не произойдет после нескольких ее изменений. Кроме того,
в первом примере все переменные инициализируются в одном месте, из-за чего создается впечатление, что все они используются на протяжении всего метода, тогда как на самом деле переменная
done вызывается только в его конце. Наконец, в ре- зультате изменений программы (которые неизбежно придется вносить, и не толь- ко при отладке) код, использующий переменную
done, может оказаться заключен- ным в цикл, при этом переменную каждый раз нужно будет инициализировать за- ново. Во второй пример в этом случае придется внести лишь небольшое измене- ние. Первый пример слабее защищен от досадных ошибок инициализации.
Два этих фрагмента иллюстрируют Принцип Близости: груп- пируйте связанные действия вместе. Этот принцип предпо- лагает также близость комментариев к описываемому ими коду, близость кода настройки цикла к самому циклу, груп- пировку команд в линейных участках программы и т. д.
В идеальном случае сразу объявляйте и определяйте каждую переменную
непосредственно перед первым обращением к ней При объявлении пере- менной вы указываете ее тип. При определении вы присваиваете ей конкретное значение. Если язык позволяет (к таким языкам относятся, например, C++ и Java),
переменную следует объявлять и определять перед фрагментом, в котором она используется впервые. В идеале каждую переменную следует определять при ее объявлении:
Перекрестная ссылка О группи- ровке связанных действий см.
раздел 10.4.
>
>
236
ЧАСТЬ III Переменные
Пример хорошей инициализации переменных (Java)
int accountIndex = 0;
// использование переменной accountIndex
Переменная total инициализируется непосредственно перед ее использованием.
double total = 0.0;
// использование переменной total
Переменная done также инициализируется непосредственно перед ее использованием.
boolean done = false;
// использование переменной done while ( ! done ) {
Объявляйте переменные по мере возможности как final
или const Объявив переменную как final в Java или const
в C++, вы можете предотвратить изменение ее значения пос- ле инициализации. Ключевые слова
final и const полезны для определения констант класса, исключительно входных параметров и любых ло- кальных переменных, значения которых должны оставаться неизменными после инициализации.
Уделяйте особое внимание счетчикам и аккумуляторам Переменные i, j,
k, sum и total часто играют роль счетчиков или аккумуляторов. Нередко програм- мисты забывают обнулить счетчик или аккумулятор перед его использованием в очередной раз.
Инициализируйте данные-члены класса в его конструкторе Подобно пе- ременным метода, которые следует инициализировать при вызове каждого мето- да, данные класса следует инициализировать в его конструкторе. Если в конструк- торе выделяется память, в деструкторе ее следует освободить.
Проверяйте необходимость повторной инициализации Спросите себя, нуж- но ли будет когда-нибудь инициализировать переменную повторно: например, для применения в цикле или для переустановки ее значения между вызовами метода.
Если да, убедитесь, что команда инициализации входит в повторяющийся фраг- мент кода.
Инициализируйте именованные константы один раз; переменные иници-
ализируйте в исполняемом коде Если переменные служат для имитации име- нованных констант, вполне допустимо инициализировать их один раз при запуске программы. Инициализируйте их в методе
Startup(). Истинные переменные ини- циализируйте в исполняемом коде неподалеку от места их вызова. Очень часто метод, который первоначально применялся один раз, после изменения програм- мы вызывается многократно. Переменные, которые инициализируются в методе
Startup() уровня программы, не будут инициализироваться повторно.
Перекрестная ссылка О группи- ровке связанных действий см.
также раздел 14.2.
>
>
1 ... 26 27 28 29 30 31 32 33 ... 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).