|
Часто обнаружение той или иной ошибки в программе оказывается делом довольно трудным. Мы приходим к мысли о том, что в программу закралась ошибка, в одной из следующих ситуаций:
1. Отсутствует уверенность в том, что программа начала выполняться.
2. Программа начала выполняться, но произошел преждевременный останов с выдачей или без выдачи сообщения о системной ошибке.
3. Программа начала выполняться, но зациклилась, о чем можно судить по ее чрезмерно долгой работе.
4. Программа выдала неправильную информацию.
Любая из этих ситуаций требует от программиста проверки последовательности выполнения команд программы. Обычно для этих целей пригодны операторы TRACE, однако они обладают рядом недостатков, которые отмечаются ниже в разделе "Средства отладки".
Если ошибка оказывается непонятной и ее местоположение не обнаружено, можно повторить прогон программы и посмотреть, появится ли та же ошибка снова. Большинство ошибок воспроизводимо, и этот шаг по крайней мере подтвердит вам, что ошибка в программе есть. Неповторяющиеся ошибки могут быть вызваны неправильным действием оператора ЭВМ, сбоем в работе оборудования, колебаниями питающего напряжения или тупиковыми ситуациями в операционной системе. О таких ошибках надо сохранять какие-то записи, поскольку часто они возникают в случайные моменты времени. Одна из возможных причин неповторяющихся ошибок — наличие в программе неопределенных переменных. Регистрировать факты появления таких ошибок можно, например, посредством внесения дополнительных примечаний в документацию и хранения сообщения об ошибке на будущее.
Выявление поведения некоторого неизвестного блока в программе означает прежде всего необходимость выработки стратегии поиска. В одних случаях (например, когда единственной выходной информацией является сообщение об ошибке) вообще отсутствуют какие-либо сведения, позволяющие выявить ошибку. В других (например, когда выдан некоторый результат) программист все-таки располагает некоторыми данными о местоположении ошибки. Обычно не представляет труда исправить ошибку, если известно, где она находится.
Иногда бывает полезно исключить из рассмотрения маловероятные причины ошибок. Когда вы сталкиваетесь с какой-либо ошибкой или неполадкой, прежде всего следует решить, что тому виной: технические средства, операционная система, компилятор или ваша собственная программа? Если вы не работаете на новом оборудовании и не имеете дело с новым программным обеспечением, то большинство ошибок — ваши собственные ошибки.
Удостоверившись в наличии программной ошибки, вы можете действовать далее по методу последовательного исключения. Если вы получаете распечатки вводимых данных, то подпрограммы ввода скорее всего работают нормально. Аналогично могут быть исключены из рассмотрения некоторые другие подпрограммы, и область поиска постепенно сузится до двух-трех "подозрительных"
блоков, которые и подвергаются более глубокому анализу. Глав* ный принцип в работе по обнаружению ошибки —это недопущение беспорядочного поиска, к которому склонны многие неопытные программисты.
Полезно иметь в виду, что поиск ошибок необходимо начинать с рассмотрения в первую очередь простых ситуаций. Например, если вы считаете, что ошибка располагается в какой-то из трех подпрограмм и проверка каждой из них занимает соответственно 10 мин, 2 ч и 3 дня, то явно имеет смысл проанализировать сначала две первые подпрограммы, так как при этом есть возможность получить желаемый результат за короткое время. (Следует заметить, что обычно руководствуются именно этим неписаным правилом.)
Цель программиста состоит в том, чтобы продолжать сужение области поиска до полного выявления ошибки. Поскольку одни блоки программы могут характеризоваться нулевой вероятностью наличия в них ошибки, а другие —довольно высокой, задача заключается в постепенном увеличении вероятности определения местоположения ошибки до 100%.
Общепринятый метод отыскания ошибок в программе предполагает введение в нее от 5 до 10 специальных отладочных операторов. Один^ из- них должен находиться в начале программы, другой— в конце, а остальные следует располагать внутри программы равномерно, но избегая попадания их внутрь циклов. Следует-размещать один оператор до начала цикла и один после его окончания; в противном случае соответствующая информация будет выдаваться на печать каждый раз при выполнении цикла, и нетрудно представить себе случай, когда цикл повторяется не одну тысячу раз.
Моими излюбленными отладочными операторами, используемыми для поиска ошибок в программе, являются операторы вида
DEBUG 1 DEBUG 2
Первый из них обеспечивает печатание слова DEBUG и цифры 1; второй — печатание того же слова с цифрой 2. Слово DEBUG указывает, что данный оператор введен в программу только для отладочных целей и подлежит исключению из нее по окончании отладки. Встраивание таких операторов в программу позволяет легко обнаруживать местоположение ошибки.
Пусть, например, в программе имеются 10 отладочных операторов; тогда выдаваемая на печать информация может оказаться следующей:
DEBUG 1 DEBUG 2 DEBUG 3 DEBUG 4
Поскольку последнее напечатанное предложение DEBUG 4, очевидно, что ошибка располагается где-то между отладочными операторами DEBUG 4 и DEBUG 5. Если анализ указанной части программы не выявляет в ней ошибки, то необходимо разместить все 10 отладочных операторов в том же блоке, границы которого ранее отмечались операторами DEBUG 4 и DEBUG 5. В конце концов этот процесс приведет к обнаружению ошибки.
Однако с отысканием неверно действующего оператора работа по устранению ошибки зачастую не заканчивается, поскольку обычно оказывается, что причина ошибки находится где-то в другом месте программы.
Рассмотрим, например, оператор
С = В/А
Затруднения с .выполнением этого оператора должны возникать тогда, когда А равно нулю. Поэтому следующим шагом должна быть проверка предшествующего оператора для выяснения причины обращения А в нуль.
Процесс обнаружения ошибок характеризуется выявлением двух мест в программе: точки обнаружения и точки происхождения. Точка обнаружения — это место в программе, где ошибка себя проявляет или становится очевидной. Точка обнаружения должна выявляться первой. В рассмотренном выше примере такой точкой является оператор, использующий нуль в качестве делителя.
Точка происхождения —это место в программе, где возникают условия для появления ошибки. В нашем примере эти условия возникают там, где А присваивается значение, равное нулю. Действительная ошибка исходит не из точки обнаружения, а из точки происхождения. Точка же обнаружения служит отправным пунктом для поиска точки происхождения.
Описанный выше метод обнаружения ошибок предполагает использование ЭВМ. Однако существует еще и другой, очень часто недооцениваемый метод — это внимательное чтение листинга программы. Такой подход дает наилучшие результаты в случаях, когда область расположения ошибки сужена до небольших размеров. Был, например; проведен эксперимент с двумя группами программистов, перед которыми ставилась задача обнаружения ошибки в программе. Одна группа делала это с помощью ЭВМ, а другая — вручную. Оказалось, что обе группы затратили на обнаружение ошибки одинаковое время.
4.23.1. ОТЛАДОЧНАЯ ИНФОРМАЦИЯ
Информация, выдаваемая на печать в процессе отладки программы, состоит из операторов, характеризующих результаты выполнения отладочных действий. Первым таким действием является эхо-печать всех введенных данных, затем необходима информация о ходе вычислительных операций и в заключение — информация о работе логической части программы.
Бели в программе выполняется большой объем вычислений, то неверный результат редко предоставляет информацию, достаточную для нахождения местоположения ошибки. Поэтому необходимы специальные отладочные средства. Прежде всего переменные, выдаваемые на печать в ходе отладки, должны иметь удобные идентификаторы. Не следует воспринимать как что-то необычное такой стиль программирования, когда значительная часть программы пишется специально для облегчения процесса отладки. Вместе с тем вводимые в исходную программу отладочные блоки должны иметь такую структуру, чтобы их исключение не пр'иводило к программным ошибкам. Поскольку программа в ходе отладки компилируется 10—20 раз, выгодно предусматривать выдачу отладочной информации на возможно более ранних этапах, с тем чтобы уменьшить количество необходимых циклов компилирования.
Наиболее эффективны в этом отношении отладочные средства, предусматриваемые в программе самим программистом. Обычно легче это сделать в процессе программирования, а не на более поздней стадии отладки. Очевидно, что после окончательной отладки и тестирования программы отладочные блоки должны быть из лее исключены.
К сожалению, для программистов типична ситуация, когда на этапе написания программы о средствах отладки забывают. Причина подобного положения кроется в том, что мы не можем реально оценить, насколько эти средства действительно необходимы. Для того чтобы решить, нужно ли вводить в программу отладочные операторы, полезно в ходе программирования задаваться вопросом: достаточно ли выходной информации данного блока для обнаружения в нем ошибок, если таковые имеются?
Не следует забывать о том, что на отладку тратится больше времени, чем на все другие этапы создания программы. А во многих случаях на отладку программы расходуется даже больше машинного времени, чем на ее рабочие прогоны.
Вводите средства отладки как можно раньше.
Стратегия ввода в программу специальных отладочных операторов, предназначенных для выдачи данных на печать, обеспечивает получение четкого представления о том, что происходит при выполнении как арифметических, так и логических операций. Как правило, чем ближе к началу программы расположены такие вспомогательные операторы, тем меньше требуется в дальнейшем отладочных прогонов.
Как неоднократно подчеркивалось выше, после завершения отладки программы все отладочные операторы подлежат исключению из нее. Лучше всего преобразовать их в комментарии, чтобы прекратить выдачу отладочной информации. Тогда сохраняется возможность вернуться к ним впоследствии при необходимости. Если же их уничтожить совсем,' то в будущем в случае обнаружения ошибок в программе или внесения в нее изменений отладочными операторами воспользоваться не удастся, хотя такая необходимость и возникнет.
При наличии в распоряжении программиста системы терминального доступа имеется возможность помечать тем или иным образом строки программы, предназначенные для использования при отладке и тестировании; тогда программа-редактор сможет исключать их автоматически. Например, в случае применения языка ФОРТРАН можно начинать каждую такую строку с номера отладочного оператора 9ХХХХ, а впоследствии выдавать редактирующей программе команду на устранение всех помеченных указанным способом строк.
В заключение следует сказать, что, будучи преобразованными в комментарии, отладочные и тестовые строки программы представляют собой важную часть ее документации, так как несут информацию о конкретных проведенных испытаниях.
Язык ПЛ/1 фирмы IBM позволяет программисту задавать левую и правую границы считывания карт исходной программы посредством использования оператора SORMGIN. В нормальном режиме этот оператор имеет вид SORMGIN = (2,72), что может служить основой для автоматического переключения программы с отладочного режима или режима тестирования на рабочий. Осуществить это возможно следующим образом. При отладке и тестировании исходной программы используйте оператор SORMGIN = (4,70), а в рабочем режиме ее эксплуатации — оператор SORMGIN= (2,72). Для всех отладочных и тестовых операторов программы, которые вы хотите исключить из ее рабочего варианта, просто поместите знаки /* в колонках 2—3 и знаки */ в колонках 71—72. Поскольку указанные знаки служат ограничителями комментария, предлагаемая операция позволяет автоматически трансформировать отладочные и тестовые средства в комментарии.
4.23.2. ВЫБОРОЧНАЯ ПЕЧАТЬ
Часто в ходе отладки бывает желательно выдать на печать выборочно ту или иную информацию. Эта потребность возникает тогда, когда необходимо проверить работу программы лишь в конкретных ситуациях или если нежелательна печать излишне большого объема информации, как это может иметь место в случае попадания оператора вывода внутрь программного цикла.
Примером оператора выборочной печати может служить предложение
IF (X .LT. 0.0) WRITE ...
Этот оператор будет обеспечивать печатание данных, только когда X меньше нуля.
Для проверки индексов и выборочной распечатки отладочной информации может использоваться оператор
IF (I .GT. 10 .AND. I .LT. 15) WRITE ...
который обеспечивает выдачу на печать только в случае, если 1 = 11, 12, 13 или 14. Оператор
IF (1/5*5—1 .EQ. 0) WRITE ...
учитывает особенности выполнения арифметических действий над целыми числами. Выражение
1/5*5—1
равно нулю лишь тогда, когда I содержит 5 в качестве множителя (т. е. когда 1 = 5, 10, 15, ...). Поэтому приведенный выше оператор может использоваться для организации выдачи на печать результатов каждой /г-й итерации (в данном случае—каждой пятой) или результатов выполнения функции MOD.
Выборочные распечатки могут также применяться для контроля за числом итераций, выполненных в итеративных алгоритмах, например, с помощью оператора
IF (N .EQ. 1000) WRITE ...
Он будет сигнализировать о том моменте, когда N достигнет значения 1000, что может быть и результатом зацикливания программы. Однако в последнем случае применение указанного условного оператора связано с гораздо меньшими издержками, чем встраивание какого-либо безусловного оператора вывода внутрь цикла.
Еще одним полезным отладочным средством является оператор
IF DEBUGGING THEN ...
в котором слово DEBUGGING представляет собой логическую переменную, принимающую значения "истина" или "ложь". Такая конструкция предложения дает программисту возможность включать средства отладки (или тестирования), предусмотренные в программе путем простого присвоения соответственно значения "истина" или "ложь" единственной логической переменной. При использовании этого метода отладочные операторы могут Сохраняться в программе на случай, если в будущем возникнет потребность в их применении. Для блокирования их действия достаточно лишь присвоить переменной DEBUGGING значение "ложь". В некоторых компиляторах (например, в трансляторе с языка ПЛ/1) предусматривается возможность определения условий компилирования, которой целесообразно пользоваться для включения и выклю* чения средств отладки и тестирования.
4.23.3., ПРОСЛЕЖИВАНИЕ ЛОГИЧЕСКИХ ВЕТВЕЙ
Для указания логического пути, по которому идет выполнение программы, можно использовать те или иные операторы вывода, которые обычно вставляют либо в подпрограммы, либо непосредственно за операторами условного перехода. Благодаря этому завершение каждого этапа выполнения программы отмечается выдачей на печать соответственно подобранных сообщений. Например:
ВХОД В ПОДПРОГРАММУ МАХШМ.
ВЫХОД ИЗ ПОДПРОГРАММЫ МАХШМ.
ВХОД В ПОДПРОГРАММУ Р1ХШМ. 1
ПЕРЕХОД НА ВЕТВЬ "МЕНЬШЕ НУЛЯ".
ОДНА ТЫСЯЧА ИНТЕРАЦИИ.
Три первых комментария сообщают о том, какая из подпрограмм выполняется в данный момент; четвертый указывает конкретную ветвь, выбранную для исполнения; последнее предложение говорит о том, сколько проведено итераций. Выходные сообщения должны отмечать как желательные, так и нежелательные ситуации; в этом случае они помогают программисту выявить ошибки, которые иначе могли бы остаться незамеченными.
Существует еще одно простое средство отладки, часто недооцениваемое программистами, — это выдача всеми программами заключительных сообщений, свидетельствующих о нормальном завершении их работы. Такое сообщение должно печататься перед самым концом выполнения задания и состоять из предложения.
НОРМАЛЬНЫЙ КОНЕЦ РАБОТЫ.
Если такого сообщения не печатать, то легко просмотреть ситуацию, когда выполнение программы не будет завершено. Одновременно с сообщением об окончании работы программа может выдавать на печать итоговые сведения о том, сколько было обработано правильных и неправильных элементов информации.
4.23.4. ПУТИ ПРЕОДОЛЕНИЯ ЗАТРУДНЕНИЙ
Уместно задать вопрос: что же делать, если имеющуюся в программе ошибку выявить не удается? Как правило, в этом случае нет никакого толку от беспрерывного многодневного размышления над проблемой без посторонней помощи. Здесь можно рекомендовать два способа действий. Первый из них заключается в том, чтобы снять каким-либо образом напряжение (например, устроив легкий завтрак) или просто пойти домой. Однако, даже прекратив поиски ошибки, программист все же продолжает о ней думать. Поэтому часто после хорошего ночного отдыха найти неуловимую ошибку бывает намного легче.
Второй способ действий — это обращение за консультацией к кому-либо из коллег-программистов, который мог бы стать вашим помощником по отладке и которому вы могли бы рассказать о встретившейся ошибке. Часто бывает полезно, чтобы кто-то выслушал ваши объяснения, касающиеся постановки самой задачи и алгоритма ее решения; при этом в ходе рассуждений программист может сам обнаружить ошибку, либо у его нового партнера по отладке могут возникнуть конкретные предложения. Однако второй способ действий требует от программиста умения выслушивать советы коллеги без возражений, а если то или иное предложение кажется вам неправильным, вы можете не принимать его в расчет.
Работать с партнером по отладке надо тактично, не забывая о том, что у него тоже есть свои собственные дела. Не следует, например, просить его разобраться в распечатке, сделанной в шест-надцате- и или восьмеричном коде, или в построчной проверке текста программы, поскольку и то и другое — ваша собственная обязанность. Партнер по отладке — это тот человек, который готов понять вашу задачу, а вы с желанием делаете то же самое для него.
⇐4.22. Патология чисел || Оглавление || 4.24. Защитное программирование⇒
|