«Хороший сайт делается для людей, а поисковики приходят следом»

— из принципов редакции SeoLiga.ru
Главная » Статьи » Delphi и C++ » Отладка приложений: приёмы и инструменты

Отладка приложений: приёмы и инструменты

Любой программист на Delphi рано или поздно сталкивается с ситуацией, когда приложение «иногда вылетает», «странно считает» или «зависает у одного клиента». Сразу видно, кто умеет отлаживать, а кто нет: первый находит причину за час, второй переписывает полпрограммы наугад. Отладка — это не талант, а набор приёмов и дисциплина.

В статье разберём инструменты, которые есть в среде разработки, способы журналирования, поиск утечек памяти и типичные ловушки. Примеры ориентированы на Delphi, но принципы применимы к любому языку.

Мышление отладчика: сначала гипотеза

Главная ошибка новичка — менять код в надежде, что «может, поможет». Правильный порядок другой:

  1. Воспроизведите ошибку. Если её нельзя повторить, вы не сможете проверить исправление.
  2. Сузьте область. Определите, в каком модуле, процедуре и на каком входе она проявляется.
  3. Сформулируйте гипотезу: «значение переменной становится пустым после вызова такой-то функции».
  4. Проверьте гипотезу наблюдением, а не правкой кода.
  5. Исправьте и убедитесь, что исходный сценарий больше не ломается.

Если гипотеза не подтвердилась, это тоже результат: область поиска стала уже. Записывайте, что уже проверено, иначе через два часа вы начнёте проверять то же самое.

Точки останова и пошаговое выполнение

Базовый инструмент — точка останова. Щёлкните слева от строки, запустите программу в отладчике, и она остановится на этой строке. Дальше вы идёте по коду.

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

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

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

Окна наблюдения и вычисление выражений

Остановившись, вы хотите видеть состояние программы. Для этого есть несколько окон.

  • Локальные переменные показывают всё, что объявлено в текущей процедуре.
  • Список наблюдения хранит выбранные выражения и обновляет их на каждом шаге.
  • Вычисление и изменение позволяет прямо во время паузы посчитать выражение или поменять значение переменной и проверить, что будет дальше.
  • Стек вызовов показывает цепочку процедур, приведшую в текущую точку. Для ошибки «как мы сюда попали» это первое окно.

Умение менять значение «на лету» полезно для проверки гипотез: подставили ожидаемое значение, процесс пошёл дальше правильно, значит, причина в том, откуда значение приходит.

Исключения и их перехват

Отладчик по умолчанию останавливается на исключениях, даже если код их обрабатывает. Это раздражает, когда в программе много штатных обработчиков, но бывает полезно: вы видите место, где ошибка возникла, а не только где её «съели».

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

try
  ProcessOrder(Order);
except
  on E: Exception do
  begin
    Log('Ошибка в ProcessOrder: ' + E.ClassName + ': ' + E.Message);
    raise;
  end;
end;

Повторный вызов исключения в конце сохраняет поведение программы и одновременно оставляет след в журнале.

Журналирование: отладка без отладчика

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

Правила полезного журнала:

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

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

Утечки памяти и ресурсов

В Delphi объекты нужно освобождать вручную, если у них нет владельца. Забытый вызов освобождения даёт утечку, и приложение с ней может спокойно работать часами, а потом начать тормозить.

Как искать:

  1. Включите режим отчёта об утечках памяти при завершении программы. Менеджер памяти сообщит, какие блоки остались, и часто укажет тип объектов.
  2. Запустите сценарий несколько раз подряд и сравните отчёты. Растущее число одинаковых объектов указывает на конкретное место.
  3. Используйте шаблон «создал — защитил блоком try/finally — освободил».
  4. Проверяйте, кто владелец компонента: если передан владелец, освобождать вручную не нужно.
Obj := TStringList.Create;
try
  Obj.LoadFromFile(FileName);
  Process(Obj);
finally
  Obj.Free;
end;

Кроме памяти, утекают дескрипторы файлов, соединения с базой, сокеты, графические ресурсы. Если программа через сутки перестаёт открывать файлы, ищите незакрытые дескрипторы. Мониторинг количества дескрипторов процесса в диспетчере задач быстро покажет тенденцию.

Отладка многопоточности

Самые неприятные ошибки — те, что появляются не всегда. Гонки, взаимные блокировки, обращение к визуальным компонентам не из главного потока. Их особенность в том, что отладчик меняет тайминги, и ошибка «исчезает» под наблюдением.

Что помогает:

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

Если приложение «повисло», приостановите его в отладчике и посмотрите стеки всех потоков. Часто видно, что два потока ждут друг друга.

Отладка баз данных и сетевых запросов

Многие ошибки не в коде, а в данных. Включите трассировку SQL-запросов: что именно отправляет приложение, с какими параметрами, сколько занял запрос. Типичные находки: запрос в цикле вместо одного пакетного, параметр с неверным типом, пропущенная транзакция.

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

Воспроизведение у клиента

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

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

Типичные ловушки

  • Отладка релизной сборки с оптимизациями: переменные «пропадают», шаги прыгают. Для поиска ошибки включайте отладочную информацию и отключайте оптимизацию.
  • Старые файлы модулей: приложение собирается из устаревших скомпилированных единиц. При странном поведении выполните полную пересборку.
  • Одновременное изменение нескольких вещей. Меняйте по одному и проверяйте.
  • Доверие к собственным догадкам вместо фактов. Проверьте значение, а не предполагайте.
  • Отсутствие тестов. Каждую найденную ошибку полезно превратить в тест, чтобы она не вернулась.

Заключение

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

Со временем у вас выработается собственный набор приёмов и чутьё на подозрительные места. А пока держите под рукой простую памятку: факт важнее догадки, один шаг за раз, каждая найденная ошибка — повод улучшить журнал или добавить тест.

СЛ
Сергей Лигин
Автор SeoLiga.ru. Пишет о практике: что работает на реальных проектах, а что лучше обойти стороной. Хотите предложить свою статью? Загляните в раздел «Авторам».