Инструментирование кода.
Бывает так, что легче всего воспроизвести ту или иную проблему запустив код в «боевых» условиях. Попытки имитировать реальность в пробирке, т.е. на машине разработчика, требуют либо многочасовой установки и конфигурирования нужных компонент, либо написания кучи вспомогательного кода. В таком случае проще и быстрее инструментировать код (калька с английского «to instrument code») – добавить логику, которая будет обнаруживать ошибочное состояние и сообщать о найденной проблеме тем или иным образом.
В самом простом варианте можно просто добавить еще одно или несколько утверждений (assert). Хотя это далеко не самый лучший вариант. Например потому, что они работают только в отладочной версии программы. Кроме того стандартная реализация утверждений не отличается особой толерантностью – в случае срабатывания программа просто завершается без всяких сантиментов.
Гораздо более удобным решением является использование собственной реализации утверждений. Причем наиболее важной частью, на мой взгляд, является не то, как проверяется условие или куда выводится сообщение об ошибке, а то, как им образом утверждение уведомляет о проблеме. Сложность здесь в том, что нужно удовлетворить несколько противоречивых требований:
- Программист должен уведомляться о найденных проблемах автоматически. В идеальном случае он не должен тратить дополнительных усилий на включение проверки утверждений;
- Пользователь должен иметь возможность использовать инструментированный компонент так же как и обычный. Пользователем в данном случае может быть и тестер, которому нужно протестировать другие части кода;
- Разработчик должен иметь возможность выборочно разрешить/запретить проверку утверждений, затратив на это минимум усилий.
Есть много разных способов решить каждую из проблем, но похоже нет универсального способа решить все три одновременно.
Вот несколько вариантов, позволяющих приблизиться к идеалу:
- Использовать __debugbreak (он же DebugBreak, int 3 и т.д.). Очевидное достоинство данного метода – простота. К сожалению такой код не годится ни для чего, кроме отладки.
- Поместив __debugbreak в блок __try __except можно существенно повысить полезность первого способа:
__try { __debugbreak(); } __except (EXCEPTION_EXECUTE_HANDLER) {}Срабатывание такого утверждения не приведет к останову программы и пользователь ничего не заметит. С другой стороны, разработчик увидит исключение первого шанса (first chance exception) STATUS_BREAKPOINT в отладчике.
- Предыдущий вариант можно сделать еще лучше, если вместо __debugbreak использовать функцию RaiseException с кодом, уникальным для каждой обнаруживаемой ошибки. В этом случае, программист сможет избирательно игнорировать те или иные проверки. В WinDbg это можно сделать с помощью меню «Debug/Event Filters…»
- Если вы хотите ещё большего контроля, то стоит реализовать возможность чтения некоторых настроек из реестра. Это особенно удобно, если учесть, что реестр удалённого компьютера можно редактировать стандартными средствами (regedit.exe).
Естественно, что количество возможных вариантов не ограничивается предложенными выше. Если у вас есть любимый способ – поделитесь им в комментариях.
“assert … в случае срабатывания программа просто завершается без всяких сантиментов.”
Прям уж так и без сантиментов?
Сообщение assertion failed в файле таком-то строке такой-то – весьма полезная информация.
А то, что приложение завершается после assert-а – вполне логично. Более печально было бы если пользователю давалась возможность продолжить исполнение
Как вариант предлагаю создать свой assert(), который можно переключать не в два положения: “работает” (debug) и “не работает” (release), а в несколько. Например три: работает, не работает, генерирует определенное исключение (которое ловится кодом. где – в зависимости от контекста кода).
Ну хорошо. “… завершается, успев сказать “Прости!” перед смертью.”
Такие проверки нельзя делать утверждениями. За такое программистам нужно руки из попы рвать.
Здесь дело не только в том, когда должено срабатывать утверждение. Желательно ещё иметь возможность выбрать один из нескольких возможных вариантов реакции на него. Помимо завершения приложения это можеть быть: создать дамп памяти, проигнорировать все последующие срабатывания этого утверждения, разобраться в отладчике что именно привело к срабатыванию утверждения и продолжить выполнение и т.д.
А мы все больше по старинке, дебаг-принтами…
> Желательно ещё иметь возможность выбрать один из нескольких возможных вариантов реакции на него.
Ну, во-первых, если без отладчика, то кроме закрытия приложения можно всё же загрузить программу в отладчик и продолжить выполнение кода с места исключения. Во-вторых, если отладчик подключен, то есть три варианта: пропустить утверждение (продолжить выполнение кода), “повторить” (передаётся управление отладчику) и “закрыть” (завершение работы программы). Так что, не всё так плохо.
А как на счет вариантов: “проигнорировать все дальнейшие срабатывания этого утверждения”, “не допускать срабатывания утверждения, если в данный отлаживается совсем другой кусок кода”. Хочешь – не хочешь, а изобретать велосипед надо.
Отладочная печать рулит! Причем из релиза ее удалять не надо. Просто развесить в конфиги настройку направления и уровня отладки.
Вот такой хреновиной я пользуюсь (помесь отладочной печати с printf):
Debug.HexDump(io,info,”Get %d bytes from port %s: “,
Data, DataLen, DataLen, PortName);
Более-менее сложное приложение (любой клиент-сервер, большинство многопоточных) невозможно отлаживать отладчиком. Пока прошерстишь по паре функций – таймауты пройдут, и связь завершится.
Само собой. Без неё тоже никак.
Не считая разумеется отладочной печати или какого-то другого лога состояния (мужики тут рассказывают, что в отладке нового рендера Висты совершеннейшие чудеса в этом смысле), у некоторых в родном геймдеве хорошо работали ассерты с опциями не только “Abort/Retry/Ignore”, а еще “Ignore assert line” и “Ignore all”. Разумеется, включенные в релизе.
> Такие проверки нельзя делать утверждениями. За такое программистам нужно руки из попы рвать.
Интересно – почему нельзя делать утверждениями? Может, это критическое место: если указатель нулевой, то программа не должна далее выполняться.
Утверждения по традиции работают только в отладочной версии.