Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Отладка приложений на C++. Часть 6 – Поиск ошибок


DevDoc home page
 
   
 

Выпуск №8

С вами Александр Кудинов и рассылка сайта http://www.devdoc.ru. Сегодня я хочу представить еще одну статью по отладке приложений. Я планирую продолжить этот цикл после небольшого перерыва, во время которого немного отвлекусь на другие вкусности. У меня есть еще много материала по отладке - отладка COM серверов, сервисов, удаленная отладка, протоколирование работы приложений и т.п. В сегодняшнем выпуске мы узнаем существует ли универсальная методика поиска ошибок? Узнать универсальный шаблон или инструкцию, следуя которой можно найти любую ошибку в программе - мечта каждого, кто хоть раз видел, как работают профи. Было ли у Вас, что вы потратили день на поиск бага, а потом ваш коллега нашел его с первого взгляда? Статья дает ключ к пониманию того, как можно осуществлять эффективный поиск и исправление ошибок. Читайте об этом ниже.

Я хочу усовершенствовать алгоритм подготовки статей, чтобы они приносили Вам еще больше пользы. У меня постоянно возникают мысли, которыми я бы хотел поделиться. Какие - то из них я оформляю в статьи, а какие - то просто умирают из-за недостатка интереса к теме. Давайте проведем эксперимент - у Вас будет возможность заказать статью на интересующую Вас тему. Для этого просто надо написать мне письмо со своими пожеланиями. В течение следующей недели выберу наиболее интересные письма и напишу статью. Заранее прошу прощения, т.к. физически не смогу освятить все темы. Обещаю, что буду делать это по мере возможности. Хочу сразу сказать, что не собираюсь превращать рассылку в вопрос/ответ - для этого есть форумы, группы новостей и т.п.

Сообщите, что вы хотите прочитать, прямо сейчас! Только представьте, что вы можете получить Развернутый ответ на интересующую Вас тему. Пишите на мыло alexander (at) devdoc.ru или воспользуйтесь ссылкой http://www.devdoc.ru/index.php/profile/alexander, чтобы отправить мне прямое сообщение

Перед тем, как перейти к статье, я хотел бы узнать Ваше мнение еще по одному вопросу. У меня появилась мысль еженедельно проводить конкурсы по программированию с памятными призами. Мне кажется - это хорошая возможность проверить свое мастерство в нашем интересном деле. Участие в конкурсах бесплатное. Если Вам это интересно - пишите! Ссылки на мои координаты выше.

Все материалы доступны на сайте http://www.devdoc.ru Наш девиз - новые статьи каждую неделю. Ресурс находится в постоянном развитии. Если у Вас есть интересный материал, вы можете опубликовать его на сайте.

Пожалуйста, присылайте свои вопросы и пожелания к темам статей на sub12@devdoc.ru

Если вам нравиться эта рассылка рекомендуйте ее своим друзьям. Подписаться можно по адресу http://subscribe.ru/catalog/comp.soft.prog.devdoc


Постоянная ссылка на статью: http://www.devdoc.ru/index.php/content/view/debugging_p6.htm

Автор: Кудинов Александр
Последняя модификация: 2007-02-16 21:58:17

Отладка приложений на C++. Часть 6 – Поиск ошибок

Введение

Меня часто спрашивают, существует ли какая-нибудь универсальная методика поиска ошибок в программе. Первый раз, когда мне задали этот вопрос, я немного растерялся. За несколько лет программирования мне не приходило в голову формализовать эту область. Для меня программирование стало таким же естественным, как разговор на родном языке. Т.е. не надо переводить мысль на другой язык, т.к. начинаешь мыслить другими категориями. Это как говорить на иностранном языке без перевода слов на родной. Попытка сформулировать сходу «технологию» поиска ошибок ни к чему не привела и заставила задуматься над этим вопросом. Когда-то давно нам читали лекции по теории ошибок. Преподаватель предлагал методику, когда все ошибки записываются и дается способ их исправления. Список ошибок был очень внушительным… и бесполезным. На мой взгляд практический аспект теории ошибок развит крайне слабо. Предложенный метод может быть хорош для тех, кто ничего не знает о программировании (и не хочет знать), но программу писать надо. Такие люди существуют, поэтому и метод может быть использован. Если же программирование - это ваш мир, - надо искать что-то другое.

Программирование - это одновременно творческая и интеллектуальная работа. И знания - не показатель мастерства. Опыт тоже имеет огромное значение. К сожалению, я не знаю методики, которая позволяла бы передавать не знания, но опыт. Только каждодневная практика позволит выработать свою индивидуальную систему поиска ошибок.

В этой статье я хочу дать несколько рекомендаций и технических приемов, которые помогут в наработке опыта. Статья – только начало этой перспективной темы. Впереди еще много исследований, чтобы предложить программистам наиболее эффективный путь борьбы с ошибками. Все, кому интересно развитие этого направления, могут связаться со мной по ссылке: http://www.devdoc.ru/index.php/profile/alexander.

Начинаем поиск

В Части 2 я приводил простейшую классификацию ошибок. Поиск для каждого типа багов – это отдельный процесс. Наиболее часто встречаются критические ошибки, которые приводят к падению программы, разрушению данных или к некорректной работе программы. Поэтому в соответствии с правилом 80/20 начнем с них. Мне не известны формализованные алгоритмы поиска ошибок.

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

Не следует относиться к этим и последующим советам как к догме. Их надо пропустить через призму своего восприятия и найти то, что подходит лично Вам. Так, например, нет смысла тратить целый день на то, чтобы минимизировать количество шагов для воспроизведения ошибки. Во всем надо искать разумный компромисс.

Замечание. Если ошибка воспроизводится нестабильно, тому может быть несколько причин. Наиболее распространенные ошибки - работа с памятью и неправильная синхронизация нескольких потоков. Об этом будет сказано ниже.

Когда вы можете стабильно воспроизвести ошибку любое количество раз – поиск ошибки значительно упрощается. Программирование - это творчество, подкрепленное солидной теорией. Поэтому чаще применяйте свое воображение. Я настоятельно рекомендую подумать о возможных путях поиска ошибки какое-то время. Для кого-то достаточно пары секунд, а для кого-то минуты. Это необходимый шаг. Думаю, что каждый программист может рассказать случай, когда он тратил прорву времени на поиск бага, а потом выяснялось, что он был тривиальным. Все дело в том, что рутина имеет обыкновение затягивать. Программист может потерять чувство времени, трассируя раз за разом одни и те же участки кода. Поэтому очень важно представить, что надо СПЕЦИАЛЬНО сделать, чтобы в программе появился такой баг. Чем больше вариантов вы сможете придумать – тем лучше. Далее надо проверить каждую гипотезу. Если ни одна из них не подошла – придумать новую и повторить процесс.

Главное - помнить еще одно правило. Если текущая стратегия поиска не приводит к положительному результату – это повод, чтобы остановиться и придумать другую. Ведь очевидно, что если раз за разом делать одно и то же, то и результат будет тем же.

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

Более 70% ошибок в программах – тривиальные. Поэтому не надо пытаться трассировать код ОС, разглядывать ассемблерный код, разбираться в алгоритмах оптимизации компилятора и делать похожие глупости. Такие серьезные действия в реальности необходимы в единичных случаях. Начинайте отрабатывать самые простые варианты причин ошибки. В первую очередь ошибки в программе – человеческий фактор. Недостаточно проработаны алгоритмы, ошибки проектирования и даже элементарные опечатки при наборе текста.

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

Ну и конечно, существенную роль играет доскональное знание функций отладчика и умение их применять. Мне иногда приходится видеть, как программисты комментируют пару строк кода и вставляют отладочные условия в C++ код. И все это ради того, чтобы сделать один или два прогона. В таких ситуациях гораздо производительней использовать условные точки останова, возможность пропускать стоки кода при выполнении, делать переходы в любую точку отлаживаемой функции и т.п. Разница в эффективности отладки становится очевидной на больших проектах, когда компиляция может занимать десятки минут.

В клинических случаях могут потребоваться радикальные меры, несмотря на их трудоемкость. Наиболее известный метод – это отключение модулей или комментирование участков кода. Суть метода очень простая. Надо начинать комментировать код до тех пор, пока не пропадет «фантастическая» ошибка. Иногда может потребоваться комментировать до 50% проекта. Затем надо понемногу убирать комментарии пока не будет локализован участок, который вызывает ошибку. Я рекомендую не увлекаться этой методикой и использовать её только в крайнем случае, когда уже ничего не помогает. Дело в том, что такой метод «тыка» отнимает много времени и, если есть возможность, надо применять более эффективные стратегии.

Если вы разрабатываете многопоточное приложение, надо заранее тщательно прорабатывать схему взаимодействия между потоками. Выделить данные, которые могут быть доступны из нескольких потоков. И только потом приступать к вдумчивому кодированию. В идеале надо многократно проверить синхронизацию между потоками, т.к. поиск ошибок в многопоточных программах - весьма трудоемкое занятие. Поэтому писать надо все за один раз и не надеяться на то, что найдете ошибки по ходу работы. Надо иметь в виду, что потоки работают по-разному на одно- и много- процессорных системах. При отладке многопоточных приложений можно сделать обертки над объектами синхронизации. Это может помочь трассировать порядок их захвата и освобождения. Также это может помочь в поиске deadlock. Можно использовать автоматизированные средства поиска ошибок синхронизации потоков. Надо только помнить, что это не панацея. Я еще не видел ни одного инструмента, который бы находил все возможные ошибки синхронизации потоков.

Технический анализ

Язык С++ обладает потрясающей мощью благодаря тому, что он не накладывает ограничений на применение низкоуровневых операций. Так, можно произвольно манипулировать участками памяти через указатели. Кому не хватает его богатых возможностей, может сделать ассемблерные вставки для выполнения практически любых действий. Обратной стороной является большая сложность языка и вероятность допускать ошибки на ровном месте.

Ошибки работы с памятью очень распространены, особенно среди новичков. Частично этого можно избежать, если с блоками памяти работать через STL, умные указатели и т.п. По сути, это искусственное ограничение и отказ работать через указатели напрямую. С одной стороны, резко снижается количество ошибок, а с другой - падает производительность. Это особенно важно для приложений, которые обрабатывают большие объемы данных. Поэтому надо выбрать золотую середину.

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

Мне очень нравиться BoundsChecker из пакета DevPartner. Этот пакет содержит целый набор утилит для поиска ошибок в потоках, вызовах API функций, при работе с памятью и т.п. Также с его помощью можно искать утечки памяти и замерять производительность приложения. Справедливости ради стоит сказать, что работает он не безупречно. Иногда он сам может вносить ошибки в программу, и скорость работы оставляет желать лучшего. Тем не менее, он сэкономил мне много времени при поиске ошибок. Часто с помощью него удавалось найти проблемные места, которые по-другому найти было бы очень проблематично. В результате – стабильная работа программы. На рынке существуют и другие пакеты для технического анализа. У всех есть свои преимущества и недостатки. Поэтому выбор инструмента - это дело вкуса и финансовых возможностей.

Исправляем ошибку

Итак, ошибка найдена! Дальше уже все совсем просто – несколько минут, и она исправлена. В большинстве случаев это именно так и есть. 90% времени тратится на поиск ошибки и 10% на ее исправление. Бессмысленно давать конкретные рекомендации по исправлению багов, т.к. мы скатимся к списку, о котором я говорил во Введении. Тем не менее, я хочу обратить ваше внимание на несколько вещей.

Перед тем, как исправлять ошибку, целесообразно на время остановиться и понять причину ее возникновения. Я видел, как разваливались проекты только из-за того, что ошибки аккуратно исправлялись, но не устранялась причина их возникновения. Например, некоторая функция получает в качестве аргумента нулевой указатель, пытается по нему записать и падает. Можно сделать специальную обработку такой ситуации внутри функции и ошибка исчезнет. Но причина осталась. Если не предполагалось, что функция будет вызываться таким образом – значит ошибка в вышестоящем коде. Это может быть все что угодно – цикл, который выходит за границу массива, чтение неинициализированной памяти, нарушенное взаимодействие между модулями и т.п. Фактически, своим исправлением программист маскирует реальную ошибку. Хуже того, это может вносить новые ошибки, потому что со временем программа становится похожа на лоскутное одеяло. Логика работы просто заслоняется неимоверным количеством заплаток.

Перед тем, как вносить изменения в код, надо разобраться в том, как он работает. Это особенно актуально, если код писался много месяцев назад, и детали реализации стерлись из памяти. Такую операцию просто обязательно делать, если вы копаетесь в чужом коде. Надо полностью понимать алгоритм работы перед внесением изменений.

Ошибки надо стараться исправлять, начиная с верхних логических уровней. Нет смысла править ядро системы, подстраивая его под баг.

Бывает полезно исправлять ошибки через выполнение Рефакторинга. Это позволяет значительно улучшить качество кода и дает определенную гарантию, что в будущем этот код не будет рассадником багов. Такой подход следует применять, если вы видите, что программа превращается в «лоскутное одеяло». Таким образом, использование Рефакторинга позволяет улучшать наиболее проблемные места программы.

Заключение

Существует множество методик поиска и устранения багов. Они могут существенно отличаться для разных программ разного назначения. В статье я описал только ряд базовых моментов, которые могут дать толчок к совершенствованию лично вашей техники отладки приложений.

Существует множество технических приемов по локализации багов. Например, если программа упала у клиента – то в вашем распоряжении будет минимум информации: адрес инструкции, которая вызвала исключение и может быть, неграмотное описание что произошло. Надо хорошо владеть приемами работы с символьной информацией, чтобы это было полезно.

Я не вижу необходимости перечислять все такие тонкости. В конечном итоге это будет тот же список, о котором я говорил во введении. Безусловно, он более полезен, чем перечень возможных ошибок, но малоприменим на практике. Его можно рассматривать только как перечень идей, которые могут быть опробованы на практике.

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

Продолжение следует...

Copyright (C) Kudinov Alexander, 2006-2007

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


В избранное