Инструментирование кода

Бывает так, что легче всего воспроизвести ту или иную проблему запустив код в «боевых» условиях. Попытки имитировать реальность в пробирке, т.е. на машине разработчика, требуют либо многочасовой установки и конфигурирования нужных компонент, либо написания кучи вспомогательного кода. В таком случае проще и быстрее инструментировать код (калька с английского «to instrument code») - добавить логику, которая будет обнаруживать ошибочное состояние и сообщать о найденной проблеме тем или иным образом.

В самом простом варианте можно просто добавить еще одно или несколько утверждений (assert). Хотя это далеко не самый лучший вариант. Например потому, что они работают только в отладочной версии программы. Кроме того стандартная реализация утверждений не отличается особой толерантностью – в случае срабатывания программа просто завершается без всяких сантиментов.

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

  1. Программист должен уведомляться о найденных проблемах автоматически. В идеальном случае он не должен тратить дополнительных усилий на включение проверки утверждений;

  2. Пользователь должен иметь возможность использовать инструментированный компонент так же как и обычный. Пользователем в данном случае может быть и тестер, которому нужно протестировать другие части кода;

  3. Разработчик должен иметь возможность выборочно разрешить/запретить проверку утверждений, затратив на это минимум усилий.

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

  1. Использовать __debugbreak (он же DebugBreak, int 3 и т.д.). Очевидное достоинство данного метода – простота. К сожалению такой код не годится ни для чего, кроме отладки.

  2. Поместив __debugbreak в блок __try __except можно существенно повысить полезность первого способа:

__try
{
    __debugbreak();
}
__except (EXCEPTION_EXECUTE_HANDLER)
{}

Срабатывание такого утверждения не приведет к останову программы и пользователь ничего не заметит. С другой стороны, разработчик увидит исключение первого шанса (first chance exception) STATUS_BREAKPOINT в отладчике.

  1. Предыдущий вариант можно сделать еще лучше, если вместо __debugbreak использовать функцию RaiseException с кодом, уникальным для каждой обнаруживаемой ошибки. В этом случае, программист сможет избирательно игнорировать те или иные проверки. В WinDbg это можно сделать с помощью меню «Debug/Event Filters…»

  2. Если вы хотите ещё большего контроля, то стоит реализовать возможность чтения некоторых настроек из реестра. Это особенно удобно, если учесть, что реестр удалённого компьютера можно редактировать стандартными средствами (regedit.exe).

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

comments powered by Disqus