Тестирование-книга. Ю. Н. Артеменко Научный редактор
Скачать 6.27 Mb.
|
Реентерабельность Реентерабельная программа может параллельно использоваться несколь кими процессами. Реентерабельная подпрограмма может вызывать сама себя или быть вызвана несколькими параллельными процессами одновременно. Некоторые языки программирования не поддерживают такой возможнос ти, т.е. повторного входа в процедуры, и при попытке его выполнения 5 0 4 Часть III: Управление проектами и группами программа разрушается. Но даже если язык программирования позволяет процедуре обслуживать несколько процессов, эта возможность сопряжена с целым рядом проблем. В частности, как раздельно хранить данные, пред назначенные для различных процессов, чтобы один процесс не смог раз рушить данные другого? Имена переменных, содержащие имена команд Некоторые языки программирования или их диалекты игнорируют пробелы. В таком языке строка PRINTMYNAME может быть интерпрети рована как PRINT MYNAME. Выполняя эту команду, программа попыта ется распечатать содержимое переменной с именем MYNAME. Если программист на самом деле хотел определить переменную с именем PRINTMYNAME, эта попытка приведет к ошибке. Как правило, подобные ошибки выявляют сами программисты, но некоторые из них все же сохра няются в программах. Неверное предположение о состоянии программы или данных после вызова Предположим, что в программе имеется процедура, предназначенная для установки определенного параметра внешнего устройства, например, его скорости передачи информации. Программа вызывает эту процедуру и полагает, что она успешно выполнила свою работу. Она немедленно начи нает передачу данных. Однако на этот раз установка скорости не была выполнена. В результате не удается и передача данных, а программа "за висает" в ожидании ответа устройства. Другим примером может быть выполнение подпрограммы, масштабиру ющей данные и возвращающей число между 1 и 10. При определенных обстоятельствах эта подпрограмма ведет себя нестандартно, возвращая масштаб в диапазоне от 0 до 10. Поскольку вызывающая подпрограмма не ожидает получить 0, она аварийно завершается при попытке деления на 0. Обработка ошибок выполнения процедур Предположим, что процедура вычисляет квадратный корень из передан ного ей числа. Если ей передано отрицательное число, она ничего не вы числяет и перед завершением устанавливает флаг ошибки. В данном случае флаг ошибки используется для того, чтобы вызывающая процедура сама могла решить, что ей делать с проблемой. В одном случае может быть выведено сообщение об ошибке, в другом отображена подсказка, а в тре тьем число может быть передано менее скоростной подпрограмме, вычис ляющей квадратные корни из комплексных чисел. Процедуры, Приложение: Распространенные программные ошибки 5 0 5 возвращающие информацию об ошибках в виде флагов, могут вызываться в самых разных ситуациях. В любом случае вызывающая подпрограмма обязательно должна проверить, выполнила ли процедура то, что предпола галось программистом. Однако, если ошибка очень маловероятна, програм мист может забыть о проверке результата. При тестировании, если ошибка все же произойдет, она может показаться невоспроизводимой. Возврат не в ту точку кода Главным отличием между вызовом подпрограммы и переходом по ко манде GOTO является то, что из подпрограммы управление всегда возвра щается в точку вызова, в то время как после перехода по GOTO возврат вообще не выполняется. Однако иногда управление после вызова подпрог раммы может быть возвращено не в то место программного кода. Испорченный стек После завершения работы подпрограммы управление передается коман де, непосредственно следующей за командой вызова. Для хранения адре са этой команды используется структура данных, называемая стеком (stack). В верхней ячейке стека хранится адрес, помещенный туда последним. Именно по нему и возвращается управление после завершения подпрограм мы. Как правило, стек используется не только для хранения адресов воз врата, но и для временного хранения некоторых данных. Если подпрограмма помещает в стек некоторые данные и не удаляет их перед своим завершением, в верхней ячейке стека будет вовсе не адрес возврата. Однако компьютер этого не узнает и выполнит передачу управ ления, интерпретировав то, что он найдет в стеке, как адрес. В результате управление будет передано совершенно другому участку памяти. Если там окажутся данные, сбой произойдет немедленно, а если какой-то код, про грамма начнет делать что-то не то и, скорее всего, тоже вскоре аварийно завершит работу. Переполнение и выход за нижнюю границу стека Стек обычно предназначен для хранения фиксированного количества адресов — например, не более 16, 32 или 128. Предположим, что стек вмещает не более двух адресов. Программа вызывает процедуру 1 и сохра няет в стеке адрес возврата. Затем процедура 1 вызывает процедуру 2 и тоже сохраняет адрес возврата. Когда процедура 2 завершает свою работу, управление возвращается процедуре 1, а по ее завершении — главной про грамме. Но что будет, если процедура 2 вызовет процедуру 3? В стеке уже хра нятся два адреса, и адрес возврата из процедуры 3 в него не поместится. Такая ситуация называется переполнением стека (stack overflow). Обычно 5 0 6 Часть III: Управление проектами и группами программа или центральный процессор разрешают ее тем, что просто уда ляют из стека самое старое из хранящихся в нем значений и помещают в него новое. После этого из подпрограммы 3 управление передается под программе 2, из подпрограммы 2 подпрограмме 1. А далее... куда переда вать управление после завершения подпрограммы 1 — неизвестно. Такая ситуация называется выходом за нижнюю границу стека (stack underflow). ВЫХОД ИЗ подпрограммы по GOTO вместо RETURN Подпрограмма 1 вызывает подпрограмму 2. Последняя, вместо того чтобы нормально завершиться, осуществляет переход в процедуру 1 по команде GOTO. При этом адрес возврата из процедуры 2 остается в сте ке. После завершения процедуры 1 выполняется переход по хранящемуся в стеке адресу — обратно в процедуру 1! Как правило, это не намеренное действие программиста, а ошибка. Чтобы ее избежать, процедура 2 долж на сама удалить из стека свой адрес возврата и только потом осуществить переход в процедуру 1. Такая технология программирования чревата огром ным количеством ошибок (выходами за нижнюю границу стека, возвратом не в ту процедуру, путаницей с возвращаемыми данными и т.п.), поэтому лучше всего ею не пользоваться. Прерывания Прерыванием называется специальный сигнал, по которому компьютер приостанавливает выполнение текущей программы и передает управление процедуре обработки прерываний. Позднее выполнение программы может быть продолжено. Типичными примерами событий, вызывающих прерыва ния, являются события ввода/вывода, включая сигналы, поступающие от системного таймера. Неверная таблица прерываний Получив сигнал прерывания, процессор должен найти процедуру его обработки и передать ей управление. Адрес этой процедуры (возможно, вместе с некоторой дополнительной информацией) хранится в определен ном месте памяти и называется вектором прерывания. По нему и осуществ ляется переход. Если компьютер различает несколько типов прерываний, в его памяти хранится список адресов процедур их обработки, называемый таблицей прерываний. Если таблица прерываний по каким-то причинам содержит неверную информацию, в ответ на прерывания возможны самые разнообразные ошибки. Если, например, векторы прерываний просто поменялись места ми, программа может вести себя странно, пытаясь, например, отобразить на экране введенный символ в ответ на сигнал от системного таймера или реагировать на нажатия клавиш как на сигналы тайм-аута. Приложение: Распространенные программные ошибки 5 0 7 Ошибки, связанные с модификацией программами таблицы прерываний Программа может модифицировать таблицу прерываний, записав в нее новые адреса. Если это делается на время работы определенного модуля, перед завершением он должен восстановить исходные векторы прерываний. Возможно также, что программа запишет в таблицу прерываний неверный адрес или вообще ее запортит. В результате после очередного прерывания компьютер перейдет не к той подпрограмме или вообще "зависнет". Ошибки, связанные с блокированием прерываний Программа может блокировать большинство прерываний, в результате чего компьютер перестает на них реагировать. Например, в некоторых случаях большая часть прерываний блокируется перед началом записи данных на диск и разблокируется после ее окончания. Это предотвращает многие ошибки передачи данных. Неудачное возобновление работы программы после прерывания Выполнение программы было прервано, а затем возобновлено. В неко торых системах после возобновления программа получает сообщение или иное указание, что она была прервана. В сообщении обычно указывается тип прерывания (от клавиатуры, таймера, модема и т.п.). Это очень полез но. Например, программа может обновить изображение на экране, предпо лагая, что в ходе обработки прерывания оно могло быть изменено. Однако во встроенном в программу блоке обработки прерывания программист может допустить ошибку, например, передать дальнейшее управление не той подпрограмме. С ошибками такого рода программисты так же не лю бят иметь дело, как и с ошибками в блоках обработки ошибок, и так же часто их пропускают. Завершение работы программы Некоторые языки программирования останавливают программу, когда в ней происходят ошибки определенных типов. Бывает также, что ни язык программирования, ни программист не предполагали остановки програм мы, но она все же останавливается. Не каждое прекращение работы программы происходит вследствие ошибки, связанной с управлением потоком. Если завершение работы при определенном условии определяется программным кодом и программа завершается неожиданно для пользователя, значит, речь идет об ошибке пользовательского интерфейса. 5 0 8 Часть III: Управление проектами и группами "Зависание" компьютера "Зависнув", компьютер прекращает реагировать на клавиатурный и иной ввод, прекращает печатать, индикаторы не меняют своего состояния, но при этом могут и гореть. Единственным способом, позволяющим вос становить работоспособность системы, является аппаратный перезапуск компьютера (выключение и включение питания или нажатие кнопки Reset). Как правило, причиной "зависания" компьютера становятся бесконеч ные циклы. Если операционная система позволяет прикладным програм мам полностью захватывать управление, программа может, например, бесконечно ждать ответа какого-либо устройства. Если же операционная система не позволяет программам блокировать компьютер, у пользователя остается возможность выгрузить "зависшую" программу из памяти без перезапуска компьютера и не повредив работе остальных активных прило жений. Синтаксические ошибки, сообщения о которых отображаются во время выполнения программы Если программа написана на интерпретируемом языке программирова ния, синтаксическая проверка ее текста может не выполняться до тех пор, пока программа не будет запущена. Интерпретатор по очереди анализирует и выполняет каждую команду программы. Наткнувшись на команду, не поддающуюся интерпретации, он выводит на экран сообщение об ошибке и останавливает программу. Виновницей ситуации оказывается команда, которая содержит синтаксическую ошибку. Очевидно, что программист ни разу не выполнил данный фрагмент кода. Ожидание невозможных условий или комбинаций условий Программа останавливается ("зависает") в ожидании события, которое никогда не наступит. Вот несколько распространенных примеров. • Проблемы ввода/вывода. Компьютер посылает данные поломанному выходному устройству и бесконечно ждет его ответа. Подобная си туация возможна и при обмене данными между двумя процессами, когда один процесс посылает сообщение другому и ждет его ответа, а тот почему-либо не отвечает. • Взаимная блокировка. Это классическая проблема многозадачных систем. Две программы работают параллельно. Обеим нужна одна и та же пара ресурсов (скажем, принтер и дополнительная память под буфер печати). Программы захватывают по одному ресурсу и ждут, пока освободится второй. Приложение: Распространенные программные ошибки 5 0 9 • Простая логическая ошибка. Например, программа должна вводить числа от 1 до 5, отвергая любые другие данные. Однако в ней зап рограммировано следующее условие допустимости числа: IF INPUT > 5 AND INPUT < 1. Такому условию не соответствует вообще ни одно число, поэтому программа отвергает любой ввод и ждет бесконечно долго. Подобным же образом в многозадачной системе один процесс может бесконечно долго ждать получения от другого невозможных данных. Неверный приоритет пользователя или процесса В системах с приоритетной многозадачностью компьютер, параллельно выполняющий несколько программ, периодически переключается между ними. Он некоторое время выполняет одну программу, затем переключа ется к другой, некоторое время выполняет ее, затем переключается к тре тьей и т.д. по кругу. Переключение к определенной программе выполняется в случае, когда наступило связанное с ней событие (например, пользова тель нажал клавишу) или она просто ждет слишком долго. Если две про граммы ждут одинаково долго, активизируется та, приоритет которой выше. (Приоритет назначается системой либо самой программе, либо ее пользо вателю.) В такой системе задача с низким приоритетом может ожидать своей очереди выполнения несколько часов. Иногда так и должно быть, но воз можно, что приоритет просто неправильно назначен. Если задержки в выполнении программы не столь очевидны, ошибку, связанную с непра вильным назначением приоритета, крайне трудно заметить, если только она не приведет к ситуации гонок. Циклы Существует несколько разновидностей программных циклов, но все они очень схожи. Вот пример. 1 SET LOOP_CONTROL = 1 2 REPEAT 3 SET VAR = 5 4 PRINT VAR * LOOP-CONTROL 5 SET LOOP_CONTROL = LOOP_CONTROL +1 6 UNTIL LOOP_CONTROL >5 7 PRINT VAR Программа присваивает переменной LOOP_CONTROL значение 1, переменной VAR значение 5, печатает произведение значений этих двух переменных, увеличивает значение LOOP_CONTROL на 1 и затем прове ряет, не превысило ли ее значение число 5. Поскольку значением 5 1 0 Часть III: Управление проектами и группами LOOP_CONTROL пока является 2, программа повторяет код тела цикла (команды 3, 4 и 5). Так продолжается до тех пор, пока переменная LOOP_CONTROL не получит значение 6. После этого программа перехо дит к следующей за циклом команде (ее номер 7) и печатает значение переменной VAR. Переменную LOOP_CONTROL называют переменной управления циклом. Ее значение определяет, сколько раз выполнится тело цикла. Для управления циклом не обязательно используется одна переменная, неред ко после ключевого слова UNTIL стоит целое выражение, включающее целый ряд переменных. Но в обоих случаях возможны одни и те же ошиб ки. Бесконечный цикл Если условие выхода из цикла никогда не наступает, программа может выполнять составляющие его команды бесконечно. Такая ситуация назы вается зацикливанием. Если модифицировать вышеприведенный фрагмент кода таким образом, чтобы цикл выполнялся, пока значение переменной LOOP_CONTROL не станет меньше 0, этот цикл никогда не завершится. Неверное начальное значение переменной управления циклом Предположим, что в вышеприведенной программе далее стоит оператор GOTO, передающий управление команде в строке 2. Переменная LOOP_CONTROL может иметь теперь какое угодно значение. Если про граммист предполагал, что тело цикла будет выполнено пять раз (как если бы переход был выполнен не ко второй, а к первой строке), он может быть очень удивлен происходящим. Случайное изменение переменной управления циклом В вышеприведенном примере значение переменной LOOP_CONTROL изменяется внутри цикла. В более длинном цикле, особенно если из него вызываются другие подпрограммы, имеющие доступ к переменной LOOP_CONTROL, ее значение может изменяться в нескольких местах программы, так что в результате тело цикла выполнится больше или мень ше раз, чем задумал программист. Ошибочный критерий выхода из цикла Возможно, цикл должен завершиться при условии LOOP_CONTROL >= 5, а не LOOP_CONTROL > 5. Это очень распространенная ошибка. А если критерий выхода из цикла достаточно сложен, в нем может быть даже несколько ошибок. Приложение: Распространенные программные ошибки 5 1 1 Команды, которые должны или не должны выполняться внутри цикла В вышеприведенном примере команда SET VAR = 5 помещена внутрь цикла. Но поскольку значение переменной VAR в ходе выполнения про граммы не меняется, присвоение ей значения 5 на каждом проходе цикла является пустой потерей времени. Некоторые циклы выполняются тысячи и миллионы раз, и тогда ненужные повторения могут серьезно отразиться на производительности программы. Вот если бы значение переменной VAR менялось в ходе выполнения цикла и перед каждым повторением его команд эта переменная должна была бы снова принимать исходное значение, тогда данная команда при сваивания была бы необходима именно внутри цикла, а не перед ним. Ошибка вложенности циклов Один цикл может быть вложен в другой, т.е. полностью включен внутрь него. В этом случае, если только программист не допустил ошибку, невоз можно, чтобы цикл начался внутри другого цикла, а завершился вне него. Условные операторы Условный оператор (оператор IF) имеет следующую форму. IF Условие истинно THEN |