Время от времени на форумах «пролетает» вопрос: «Какие методы отладки вы использовали?». Этот пост — мой ответ.
Отладка — это комплексный процесс по выявлению и исправлению дефектов в программном обеспечении. Сами же дефекты, обычно, обнаруживается в процессе тестирования ПО.
(Прим. Часто под термином «отладка» подразумевают «тестирование» + «непосредственно отладка». В данном посте эти термины я буду разделять.)
Отладка состоит из следующих этапов:
- воспроизведение дефекта (любым из доступных способов);
- анализ дефекта (поиск причины возникновения дефекта — root-cause);
- дизайн исправления дефекта (и возможно ревью, если есть альтернативы);
- кодирование исправления дефекта (и какие-либо активности связанные с кодированием);
- валидация исправления;
- интеграция исправления в кодовую базу или целевую систему;
- дополнительные валидации после интеграции (если необходимости).
Пункты 1 и 2 — самые длительные этапы. Они могут объединяться, например, если сложность отладки именно в воспроизведение проблемы и имеется достаточно assert-ов в коде, то тогда после воспроизведение дефекта root-cause будет автоматически выявлен за счёт детального сообщения об ошибке в assert-е. Но бывает и по-другому, когда дефект воспроизводится легко, но root-cause абсолютно не ясен.
Если root-cause дефекта найден, то разработать исправление не составляет большого труда (конечно, в зависимости от требований к качеству ПО). Поэтому с этапами 1 и 2 в большинстве случаев ассоциируется термин «отладка». Более того, отладка — это рекурсивный процесс. На любом этапе отладки могут возникнуть новые дефекты, которые придётся отлаживать. Например, какая-то часть исправления в коде работает не так как ожидается и соответственно придётся отлаживать эту часть в изоляции и снова основное время уходит на пункты 1 и 2 и т.д. Таким образом, под методами отладки дефектов я понимаю методики и подходы выполнения пунктов 1 и 2. (Прим. Другие пункты тоже достаточно важны, но они не входят в скоуп данного поста.)
Намерено не даю никакие ссылки на инструментальные средства, чтобы не делать рекламы. Методы предотвращения дефектов (защитное программирование, инспекции кода и т.п.) вне скоупа данного поста. Хотя, assert-ы как часть методики защитного программирования могут считаться отдельным вполне полезным методом отладки ПО. Также существуют различные методики рассуждений при поиске root-cause-а, но они тоже вне скоупа ;).
И так, методы отладки ПО используемые на данный момент в индустрии (которыми я пользовался) перечислены ниже.
- Запуск программы из под отладчика (софтварного, железячного или удалённого дебагера) с пошаговой отладкой, просмотром состояний (переменных, стека, памяти, регистров, тредов и т.п.) в требуемых точках исполнения программы.
- Логирования кода — вывод в файл (или консоль и т.п.) входных, выходных аргументов функций, промежуточных состояний (переменных, стека, памяти, передаваемых или получаемых каким-либо образом данных и т.п.) в процессе исполнения программы. Детальный лог является историческим описанием исполнения программы. При сложностях с воспроизведением сценария дефекта, логирование становится основной методикой отладки.
- Анализ кода без исполнения программы — поиск причин возникновения дефекта с помощью анализа исходного кода программы, проблемного контента, конфигурации, состояния базы данных и т.п.
- Анализ поведения системы или её части (в т.ч. в более простых use-case-ах) — изолирование проблемы, путём упрощения сценария (используя ручное или автоматическое тестирование). Аксиома звучит так: чем проще сценарий, тем проще отладить проблему. Если найти более простой сценарий, то отладка может упроститься.
- Unit тестирование — выполнение автоматических unit test-ов в основном изолировано (т.е. в более простых сценариях) для функций (модулей, компонентов и т.п.), и таким образом автоматическое выявление проблемных участков кода. Unit тестирование в каком-то смысле одна из разновидностей отладки путём «анализа поведения системы».
- Прототипирование — проверка функций (модулей, библиотек, и т.п.) в изоляции с помощью небольших примеров кода (прототипов). Прототип легче отлаживать, чем целевую систему. Если проблема воспроизводиться с помощью прототипа, отладка упрощается. Unit тестирование в этом смысле более эффективный метод отладки, поскольку unit test-ы выполняются автоматически и «накапливаются» для будущего реюза, а прототипы редко становятся частью системы.
- Отладка с помощью memory-dump-ов или crash-дампов (применимо в основном для анализа паник) — разновидность логирования кода, только здесь логируется не просто некая структура памяти, а целиком вся память процесса и состояния регистров, когда возникает exception. По такому дампу памяти, имея дебажные символы, можно «раскрутить» состояние программы (стеков, очередей, переменных и т.п.), в котором она находилась во время паники. Достаточно много существует инструментальных средств для выполнения этой операции.
- Отладка с помощью перехватов (hook-ов, spy-ев) — в основном используется в случаях утечки ресурсов, разновидность логирования кода. Основная идея перехват и логирование вызова функций выделения и освобождения ресурса, а также анализ состояния ресурсов (например, памяти) в требуемый момент времени или в нужной точке исполнения программы.
- Профилирование кода (если необходима оптимизация производительности) — разновидность логирования кода, хотя часто выполняется с использованием специализированных инструментальных средств (профилировщиков). Этот метод отладки позволяет получить профиль исполнения программы — сколько и какая функция, строчка кода, модуль, и т.п. отнимают процессорного времени, и таким образом найти узкие места.
- Выполнения программы (или её части) в другой среде (операционной системе, эмуляторе, симуляторе) — основная идея в том, что если нет инструментальных средств на целевой платформе, то можно спортировать код на другую платформу, где они есть. Также можно изначально писать кросс-платформенный код системы или какой-то её части, и таким образом, при необходимости практически без портирования отлаживать код на другой платформе.
- Отладка методом RPC (remote procedure call) — применимо в основном для встроенного программирования. Суть метода в возможности вызвать любую функцию (модуль и т.п.) передавая аргументы и получая результаты исполнения удалённо с одного хоста на другом вместо того, чтобы тратить время на компиляцию или обновление софта на удалённом хосте (или железке). Существуют множество готовых фреймворков (правда в основном платных), которые инструментируют код и позволяют вызывать любые функции кода через USB или IP соединения.
- Отладка путём анализа документации, дизайна, требований или ограничений модулей (программных или аппаратных) — применимо в основном для сложных и крупных проектов. Основная идея понять по имеющейся документации допустимо ли поведение, происходящее в дефекте. Например, поддерживается ли сложная комбинация одновременно работающих фич. Если поведение не поддерживается, то необходимо просто программно закрыть use-case, вместо того, чтобы пытаться глубоко анализировать код или пытаться найти root-cause в third-party компонентах.
- Отладка трансляцией кода — сложный алгоритм пишется или прототипируется на одном языке программирования (возможно медленном или интерпретируемом) с наличием всех доступных инструментальных средств (дебагера и т.п.), а потом исходный код отлаженного алгоритма транслируется в ручную или автоматически в другой язык программирования (целевой системы), для которого отсутствуют необходимые инструментальный средства. При таком подходе отладится можно на практически любом удобном для себя языке программирования, а потом заново странслировать программу на целевой язык программирования. Возможны и другие варианты, например, дисассемблерование с целью более низкоуровневого понимания, что происходит при выполнении программы. Т.е. анализируется некий промежуточный вариант кода, который в некоторых ситуациях легче отладить или понять.
- Отладка разработкой интерпретатора — это не только метод отладки, но и паттерн проектирования. Этот метод используется, когда модуль требует частых изменений (из-за плавающих требований или поддержки большого количества фич, железок и т.п.), а время построения приложения очень большое. Для ускорения процесса и гибкости пишется небольшой интерпретатор кода с наличием управляющих конструкций if, циклов, goto. При наличии такого интерпретатора разработчик сравнительно не сложно создаёт скрипты, которые можно быстрее исправить и отладить. Как упрощённый вариант такого способа отладки, например, использование дебажных флагов в коде, которые конфигурируют код и позволяют проверить разные варианты исполнения кода сделав лишь один build.
Существуют также неправильные методы отладки. Они обобщаются следующим pattern-ами:
- занимают слишком много времени;
- root-cause не обнаружен или никого не волнует в чём был root-cause;
- исправление ухудшает качество ПО, несмотря на то, что оригинальный дефект исправлен.
Однако, это тема для отдельных обсуждений.
спасиб вам)