Программирование Оглавление Тестирование

Отладка

Отладка программы - это процесс, в ходе которого осуществляется устранение ошибок. Отладка порой занимает столько же времени, сколько программист затратил на написание исходного кода. Процесс отладки итерационный. Программист устраняет одну ошибку, а взамен может появиться две новых. По этому поводу Фредерик Брукс в своей классической книге "Мифический человеко-месяц или Как проектируются и создаются программные комплексы" пишет, что иной раз, обнаружив в программе ошибку и научившись ее обходить, лучше все оставить как есть и ничего не исправлять. Такой подход возможно и справедлив в системах, пользователями которых являются люди с предсказуемым поведением. В нашем случае, нет никакой возможности научить пользователя обходить ошибки в CGI-программах. Первое, с чего мы начнем, так это с классификации ошибок:

Ошибки синтаксиса языка

Как правило, в абсолютном большинстве случаев, ловятся на стадии компиляции программы, или же, если вы работаете с интерпретируемым языком типа Perl или PHP, то при первом интерпретировании программы. Но есть один существенный момент - это когда выражение допустимо, но зависит от конкретного компилятора или интерпретатора. Например, в языке Си вполне допустимыми по синтаксису, но не по смыслу, являются выражения: s[i++]=i; printf("%d %d", i++, i++);. Результат этих строк не определен, т.к. неизвестно, в каком порядке будет инкрементироваться и вычисляться значение переменной i. Переменная не может более одного раза присутствовать в выражении, если ее значение изменяется в ходе вычисления этого выражения.

Ошибки во время выполнения

К ошибкам во время выполнения программы относятся ошибки в логике и алгоритмах программы. Ошибки в логике программы связаны с неправильной записью алгоритма, например, вместо && вы написали || или поставили точку с запятой там, где ее не должно быть. К этому же классу ошибок относится неправильное обращение с памятью. В языке Си неправильное обращение с памятью является наиболее распространенной ошибкой программистов, причем не только новичков, но и профессионалы нередко допускают ошибки подобного типа. Для языка Си - это один из самых больных вопросов и больших недостатков. Ошибки в алгоритме связаны с неправильным проектированием программы или же с изначально неправильно заложенной бизнес-логикой и функциональными требованиями. Пользователь ждет от программы то, что изначально от нее не требовалось. Ошибки во время выполнения - наиболее часто встречаемый тип ошибок и, как правило, устранение таких ошибок не представляется черес чур сложной задачей.

Ошибки логики взаимосвязанных CGI-программ

Данного типа ошибки лежат во взаимосвязанных CGI-программах. Рассмотрим в качестве примера тестовую систему (см. сайт http://test.itsoft.ru). При сдаче теста в цикле работают два скрипта. Первый показывает вопрос, а второй проверяет правильность ответа. Если в тесте 10 вопросов, то эти CGI-скрипты вызываются парно в цикле десять раз. Но что будет, если пользователь нажмет кнопку "Обновить" в броузере? Скрипт, который показывает вопрос, вызовется повторно. Что будет при разрыве модемного соединения? Отладка в таких системах значительно сложнее, т.к. вам придется наблюдать за выполнением ряда взаимосвязанных скриптов.

Ошибки многопользовательского доступа

Ошибки многопользовательских систем связаны с неправильным разграничением доступа к совместным ресурсам. Помимо файлов данных, записей в таблицах баз данных, общим ресурсов является генератор случайных чисел. На эти грабли нам пришлось наступить. Обязательно генерируйте случайные числа не только на основе времени, но и на основе уникального идентификатора процесса. В противном случае, при выполнении в один и тот же момент времени двух копий одной CGI-программы вы получите одинаковые результаты для двух пользователей. Ошибки многопользовательского доступа сложны тем, что могут не проявлять себя очень долго, до тех пор, пока в один и тот же момент времени системой ни будет запущено несколько копий одной CGI-программы.

Невоспроизводимые ошибки

Невоспроизводимые ошибки представляют собой наиболее сложный тип ошибок. Например, 29 февраля ваша система вдруг начала давать сбои. ;-) Ну 29 февраля, конечно, воспроизводится. Но бывают ошибки, которые мистическим образом появляются и исчезают. В той же тестовой системе была непонятная ошибка, которая проявлялась один раз на несколько сот случаев. Непонятным образом некоторые студенты после сдачи теста получали не результаты, а сбой системы. На исправление этой ошибки ушло два рабочих дня. Оказалось, что проблема в скрипте на JavaScript, который отправлял данные HTML-формы на сервер после истечения допустимого времени ответа на вопрос. Проблема в том, что если время подходило к концу, и пользователь нажимал кнопку "Ответить", а в это же время уже начала работать функция JavaScript form.submit(), то отправка данных HTML-формы происходила дважды, т.е. скрипт проверки правильности ответа вызывался два раза. А это за собой тянуло ошибку во взаимосвязанных CGI-скриптах, и внешнее проявление сбоя системы мы наблюдали уже при подсчете результатов, а не непосредственно сразу после двойной отправки HTML-формы. Сам код JavaScript был написан верно, и с теоретической точки зрения даже если пользователь нажимает кнопку "Отправить" в последнюю секунду, HTML-форма должна была отправляться только один раз. Но на практике все оказалось совсем по-другому. На самом деле, ничего мистического нет, или, как говорится, чудес на свете не бывает. Просто невозможно воспроизвести условия, в которых наблюдалась невоспроизводимая ошибка. Надо искать в программе случайности: одновременный доступ к одному ресурсу, генератор случайных чисел, неинициализированные переменные, некорректная работа с памятью или преобразование типов, которые могут проявлять себя не каждый раз.

Ошибки инструментария и других компонентов системы

Ошибки самого компилятора или интерпретатора. Очень редко, но и такое бывает. Например, лет пять назад мною была обнаружена следующая ошибка:
// bug BC++3.1 & MSVC++4.2 print 0, it is wrong
// watcom & others print 2, it is right
#include<stdio.h>
void main()
{
int x=10,y=8;
x>y?x-=y:y-=x;
printf("%d\n",x);
}
BorlandC3.1 и MSVisualC 4.2 выдавали ноль, а остальные компиляторы выдавали 2. Ниже идет пример еще одной, обнаруженной мной, ошибки для любителей C++
/*
msvc++5.0
*/
#include<new>
struct A{
};

typedef A TYPE;
typedef TYPE* pTYPE;

void main()
{
pTYPE p = new TYPE;
pTYPE* pp = (pTYPE*)new char[sizeof(pTYPE)];
new(pp) pTYPE(p);
//pp->~pTYPE(); // pp[0].~pTYPE(); // don't work, why?
pp[0].pTYPE::~pTYPE();
delete [] (char*)pp;
delete p;
}
Ну и чтобы совсем уж быть полным, приведу недавно обнаруженную мной ошибку в СУБД MySQL. Ее обещали исправить только в четвертой версии. Ниже приведены два SQL-запроса из программы анализа посетителей веб-сайта. Первый работает нормально. Второй очень похож на первый и полностью удовлетворяет синтаксису SQL. Но второй запрос MySQL отказывается выполнять и возвращает пустую таблицу.
SELECT COUNT(DISTINCT ip), COUNT(*), CONCAT(shref,href) FROM hit GROUP BY 3

SELECT COUNT(*), CONCAT(shref, href) FROM hit GROUP BY 2

После классификации ошибок давайте рассмотрим методы их поиска. В первую очередь, один простой и, казалось бы, очевидный совет: "надо думать, анализировать, почему программа не работает так, как было задумано, что надо в ней исправить". Никакой самый навороченный отладчик за вас ошибку не найдет. Никакая самая лучшая методика не найдет и не ускорит поиск ошибки, если вы не включите мозги по полной программе и не сосредоточитесь целиком и полностью на поимке ошибки. Итак, допустим вами, пользователем или тестирующим, было зафиксировано некорректное поведение программы. Что делать? Ниже перечислены методы в порядке их приоритетности, которые используют многие программисты.



Локализация ошибки

Программа - это черный ящик, который что-то принимает на вход и выдает что-то на выходе. Первое, что вам необходимо сделать - понять, где именно в этом черном ящике происходит сбой. Для этого, уменьшайте количество параметров, передаваемых на вход программы. Добейтесь двух малоразличимых наборов входных параметров, чтобы при одном из них программа работала, а при другом нет.

Отключите ненужные модули программы

Закомментируйте все лишнее. Тем самым, вы упростите вашу программу. Имеет смысл комментировать отдельные куски программы до тех пор, пока ошибка не исчезнет.

Отладочные печати

Распечатывайте значения переменных в узловых точках программы. В CGI-программах при непосредственной отладке можно выводить информацию в стандартный поток вывода - stdout, функция printf(const char *format [, argument ]...); Вы будете видеть значения переменных в броузере. Но такой способ не всегда годится, например, если система уже введена в эксплуатацию или же у вас невоспроизводимая ошибка. В этом случае воспользуйтесь выводом в стандартный поток ошибок - stderr, функция fprintf(stderr, const char *format [, argument ]...);. При выводе информации в стандартный поток ошибок она будет записываться в log-файл ошибок вашего веб-сайта.

Зовите на помощь коллег

Иной раз достаточно просто позвать на помощь своего коллегу со свежей головой и с незамыленным взглядом, начать объяснять ему, как тут у вас все устроено и работает, и тут вы возьметесь за голову со словами: "Семен Семеныч" (с) к\ф Бриллиантовая рука. Если такого не произойдет, то вдвоем, задавая друг другу вопросы, вы быстро найдете ошибку. Мы применяем этот способ в самых безнадежных случаях, и он, действительно, почти всегда приводит к положительному результату. Да, самое главное, после поиска ошибки обязательно угостите своего коллегу пивом, фантой или еще чем-нибудь, иначе в следующий раз он вряд ли придет к вам на помощь с особым энтузиазмом.



Отладчик

Самое последнее, к чему стоит прибегнуть, когда уже совсем ничего не остается, кроме как прозрачным взглядом наблюдать за трассировкой программы. Для CGI-программ применение отладчика не так просто, как для обычных программ. Для того чтобы применить отладчик, вам нужно задать необходимые переменные окружение, например, HTTP_REFERER или DOCUMENT_ROOT, а также вам необходимо передать CGI-параметры этому скрипту. Если вы используете библиотеку ITCGI, то она поддерживает режим отладки с командной строки.

Применяйте все эти методы в комплексе для поиска ошибок.