Лекция 7 Отладка и обработка исключительных ситуаций. Лекция Отладка и обработка исключительных ситуаций Корректность и устойчивость программных систем
Скачать 1.08 Mb.
|
Отладочная печать и условная компиляцияОдним из основных средств отладки является отладочная печать, позволяющая получить данные о ходе и состоянии процесса вычислений. Обычно разрабатываются специальные отладочные методы, вызываемые в критических точках программы - на входе и выходе программных модулей, на входе и выходе циклов и так далее. Искусство отладки в том и состоит, чтобы получить нужную информацию о прячущихся ошибках, проявляющихся, возможно, только в редких ситуациях. Хотелось бы иметь легкий механизм управления отладочными методами, позволяющий включать при необходимости те или иные инструменты. Для этого можно воспользоваться механизмом условной компиляции, встроенным в язык C#. Этот механизм состоит из двух частей. К проекту, точнее, к конфигурации проекта можно добавить специальные константы условной компиляции. Вызов отладочного метода может быть сделан условным. Если соответствующая константа компиляции определена, то происходит компиляция вызова метода и он будет вызываться при выполнении проекта. Если же константа не определена (выключена), то вызов метода даже не будет компилироваться и никаких динамических проверок - вызывать метод или нет - делаться не будет. Как задавать константы компиляции? Напомню, что проекты в Visual Studio существуют в нескольких конфигурациях. В ходе работы с проектом можно легко переключаться с одной конфигурации на другую, после чего она становится активной, можно изменять настройки конфигурации, можно создать собственные конфигурации проекта. По умолчанию проект создается в двух конфигурациях - Debug и Release, первая из которых предназначена для отладки, вторая - для окончательных вычислений. Первая не предполагает оптимизации и в ней определены две константы условной компиляции - DEBUG и TRACE, во второй - определена только константа TRACE. Отладочная версия может содержать вызовы, зависящие от константы DEBUG, которые будут отсутствовать в финальной версии. Используя страницу свойств, к конфигурации проекта можно добавлять новые константы компиляции. Можно также задавать константы условной компиляции в начале модуля проекта вперемешку с предложениями using. Предложение define позволяет определить новую константу: #define COMPLEX Как используются константы условной компиляции? В языке С++, где имеется подобный механизм, определен специальный препроцессорный IF-оператор, анализирующий, задана константа или нет. В языке C# используется вместо этого гораздо более мощный механизм. Как известно, методы C# обладают набором атрибутов, придающих методу разные свойства. Среди встроенных атрибутов языка есть атрибут Conditional, аргументом которого является строка, задающая имя константы: [Conditional ("COMPLEX")] public void ComplexMethod () {...} Если константа условной компиляции COMPLEX определена для активной конфигурации проекта, то произойдет компиляция вызова метода ComplexMethod, когда он встретится в тексте программы. Если же такая константа отсутствует в конфигурации, то вызов метода игнорируется. На методы, для которых возможно задание атрибута Conditional, накладывается ряд ограничений. Метод не должен быть: • функцией, возвращающей значение; • методом интерфейса; • методом с модификатором override. Возможно его задание для virtual-метода. В этом случае атрибут наследуется методами потомков. Атрибут Conditional, обычно с аргументом DEBUG, сопровождает модули, написанные для целей отладки. Но использование этого атрибута не ограничивается интересами отладки. Зачастую проект может использоваться в нескольких вариантах, например, в облегченном и более сложном. Методы, вызываемые в сложных ситуациях, например, ComplexMethod, имеющий атрибут условной компиляции, будут вызываться только в той конфигурации, где определена константа COMPLEX. Приведу пример работы с отладочными методами. Рассмотрим класс, в котором определены три метода, используемые при отладке: public class DebugPrint { [Conditional("DEBUG")] static public void PrintEntry(string name) { Console.WriteLine("Начал работать метод " + name); } [Conditional("DEBUG")] static public void PrintExit(string name) { Console.WriteLine("Закончил работать метод " + name); } [Conditional("DEBUG")] static public void PrintObject(object obj, string name) { Console.WriteLine("Объект {0}: {1}", name, obj.ToString()); } } В классе Testing определено поле класса: int state = 1; и группа методов: public void TestDebugPrint() { DebugPrint.PrintEntry("Testing.TestDebugPrint"); PubMethod(); DebugPrint.PrintObject(state, "Testing.state"); DebugPrint.PrintExit("Testing.TestDebugPrint"); } void InMethod1() { DebugPrint.PrintEntry("InMethod1"); // body DebugPrint.PrintExit("InMethod1"); } void InMethod2() { DebugPrint.PrintEntry("InMethod2"); // body DebugPrint.PrintExit("InMethod2"); } public void PubMethod() { DebugPrint.PrintEntry("PubMethod"); InMethod1(); state++; InMethod2(); DebugPrint.PrintExit("PubMethod"); } Этот пример демонстрирует трассировку хода вычислений, для чего в начало и конец каждого метода вставлены вызовы отладочных методов, снабжающие нас информацией о ходе вычислений. Такая трассировка иногда бывает крайне полезной на этапе отладки, но, естественно, она не должна присутствовать в финальной версии проекта. Взгляните на результаты, полученные при вызове метода TestDebugPrint в конфигурации Debug. Рис. 23.1. Трассировка вычислений в процессе отладки При переходе к конфигурации Release отладочная информация появляться не будет. |