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