пятница, 7 сентября 2007 г.

Техника быстрой локализации ошибок при отладке

Процесс отладки упрощенно состоит из нескольких этапов. Сперва необходимо добиться повторяемости ошибки. Как правило, это делает тот, кто находит и/или регистрирует баг, но в наиболее сложных ситуациях (когда ошибка возникает нерегулярно или ее зависимость от внешних факторов не очевидна) это приходится делать самим.

Далее необходимо локализовать баг в программном коде. Чтобы не читать метры кода, лучше максимально сократить тот диапазон, который в дальнейшем придется изучать, чтобы понять, какой фрагмент кода приводит к ошибке. Затем надо понять причину проблемы и, наконец, исправить.

По моему опыту, локализация ошибки – наиболее трудоемкий этап в 95% случаев. Если ошибка стала проявляться сравнительно недавно (то есть, можно указать дату и/или номер билда, когда ошибки точно не было и дату и/или номер билда, когда ошибка точно проявляется), могу предложить простую, но эффективную технику, которой сам успешно пользуюсь. Способ локализации ошибки основан на алгоритме двоичного поиска и состоит из двух последовательных действий:

  1. Локализация билда системы, когда баг стал проявляться.
  2. Локализация фрагмента кода, где проявляется баг.

А теперь – подробней.

Локализуем билд системы, когда баг стал проявляться

Фиксируем граничные билды: проверяем тот билд, где в мы уверены, что бага нет, затем проверяем билд, где мы уверены, что баг проявляется. Может показаться, что эти проверки излишни (мы ведь и так уверены), однако это не так. Неверное определение граничных билдов приведет к значительно большей потери времени и, как показывает практика, случаются не так уж и редко, так что я рекомендую не полениться и выполнить их! Границы зафиксированы и теперь ищем билд, который находится посередине, и проверяем его. Тут возможны 2 варианта. Если серединный билд содержит баг, значит он появился раньше чем мы предполагали. Если серединный билд чист перед законом, то баг пробрался в систему позже. Фиксируем новые граничные билды. Этот нехитрый процесс повторяем до тех пор, пока не найдем два последовательных билда: первый чистый, следующий за ним — с багом.

Локализуем фрагмент кода, где проявляется баг

По системе контроля версий сливаем исходный код билда без бага и фиксируем ВСЕ изменения между билдами. Тут желательно еще раз проверить, что баг действительно проявляется. Далее вносим в чистый билд половину всех изменений. Определять половину можно на глаз (аптекарская точность здесь не важна), главное, чтобы код компилировался, и мы могли проверить повторяемость бага. Снова смотрим поведение программы. Если баг стал проявляться, то уменьшаем порцию изменений вдвое. Если нет, то от оставшейся части изменений также находим половину и добавляем ее к системе. Этот процесс повторяется до тех пор, пока не обнаружатся те строки кода, добавление которых приводит к появлению бага.

Надеюсь, описал понятно. Если есть вопросы и/или требуются где-то пояснения, не стесняйтесь, спрашивайте! Техника проста, по сути – механическая работа, не требующая большого интеллекта, который следует поберечь для других случаев :).


2 комментария:

Анонимный комментирует...

А что если баг не имеет граничных билдов?
Шерстим стек вызовов и соответствующий код!?!? ;-)

Андрей Бороздин комментирует...

Не совсем так. Бессистемный просмотр кода и стека при отладке – это как надежда разбогатеть игрой в лотерею. Эффективнее и надежней воспользоваться методом гипотез и их проверки, который описан в книге Джона Роббинса (John Robbins) Debugging Microsoft® .NET 2.0 Applications.