Опубликован: 02.12.2009 | Уровень: специалист | Доступ: свободно | ВУЗ: Тверской государственный университет
Лекция 11:

Корректность и устойчивость программных систем

< Лекция 10 || Лекция 11: 123456

Классы Debug и Trace

Атрибут условной компиляции Conditional характеризует метод, но не отдельный оператор. Иногда хотелось бы иметь условный оператор печати, не создавая специального метода, как это было сделано в предыдущем примере. Такую возможность и многие другие полезные свойства предоставляют классы Debug и Trace.

Классы Debug и Trace - это классы-двойники. Оба они находятся в пространстве имен Diagnostics, имеют идентичный набор статических свойств и методов с идентичной семантикой. В чем же разница? Методы класса Debug имеют атрибут условной компиляции с константой DEBUG, действуют только в Debug -конфигурации проекта и игнорируются в Release -конфигурации. Методы класса Trace включают два атрибута Conditional с константами DEBUG и TRACE и действуют в обеих конфигурациях.

Одна из основных групп методов этих классов - методы печати данных: Write, WriteIf, WriteLine, WriteLineIf. Методы перегружены, в простейшем случае позволяют выводить некоторое сообщение. Методы со словом If могут сделать печать условной, задавая условие печати в качестве первого аргумента метода, что иногда крайне полезно. Методы со словом Line дают возможность дополнять сообщение символом перехода на новую строку.

По умолчанию методы обоих классов направляют вывод в окно Output. Однако это не всегда целесообразно, особенно для Release -конфигурации. Замечательным свойством методов классов Debug и Trace является то, что они могут иметь много "слушателей", направляя вывод каждому из них. Свойство Listeners этих классов возвращает разделяемую обоими классами коллекцию слушателей - TraceListenerCollection. Как и всякая коллекция, она имеет ряд методов для добавления новых слушателей: Add, AddRange, Insert - и возможность удаления слушателей: Clear, Remove, RemoveAt и другие методы. Объекты этой коллекции принадлежат классам, которые в качестве предка имеют абстрактный класс TraceListener. Библиотека FCL включает три неабстрактных потомка этого класса:

  • DefaultTraceListener - слушатель этого класса, добавляется в коллекцию по умолчанию, направляет вывод, поступающий при вызове методов классов Debug и Trace, в окно Output ;
  • EventLogTraceListener - посылает сообщения в журнал событий Windows;
  • TextWriterTraceListener - направляет сообщения объектам класса TextWriter или Stream ; обычно один из объектов этого класса направляет вывод на консоль, другой - в файл.

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

Помимо свойства Listeners и методов печати, классы Debug и Trace имеют и другие важные методы и свойства:

  • Assert и Fail, проверяющие корректность хода вычислений - о них мы поговорим особо;
  • Flush - метод, отправляющий содержание буфера слушателю (в файл, на консоль и так далее). Следует помнить, что данные буферизуются, поэтому применение метода Flush зачастую необходимо, иначе метод может завершиться, а данные останутся в буфере;
  • AutoFlush - булево свойство, указывающее, следует ли после каждой операции записи данные из буфера направлять в соответствующий канал. По умолчанию свойство выключено, и происходит только буферизация данных;
  • Close - метод, опустошающий буфера и закрывающий всех слушателей, после чего им нельзя направлять сообщения.

У классов есть и другие свойства и методы, позволяющие, например, заниматься структурированием текста сообщений.

Рассмотрим пример работы, в котором отладочная информация направляется в разные каналы - окно вывода, консоль, файл:

public void Optima()
{
   double x, y = 1;
   x = y - 2*Math.Sin(y);
   FileStream f = new FileStream("Debuginfo.txt", FileMode.Create, FileAccess.Write);
   TextWriterTraceListener writer1 = new TextWriterTraceListener(f);
   TextWriterTraceListener writer2 = new TextWriterTraceListener(System.Console.Out);
   Trace.Listeners.Add( writer1);
   Debug.Listeners.Add( writer2);
   Debug.WriteLine("Число слушателей:" + Debug.Listeners.Count);
   Debug.WriteLine("автоматический вывод из буфера:"+ Trace.AutoFlush);
   Trace.WriteLineIf(x<0, "Trace: " + "x= " + x.ToString() + " y = " + y);
   Debug.WriteLine("Debug: " + "x= " + x.ToString() + " y = " + y);
   Trace.Flush();
   f.Close();
}

В коллекцию слушателей вывода к слушателю по умолчанию добавляются еще два слушателя класса TextWriterTraceListener. Заметьте, что хотя они добавляются методами разных классов Debug и Trace, попадают они в одну коллекцию. Как и обещано, один из этих слушателей направляет вывод в файл, другой - на консоль. На рис. 10.2 показаны три канала вывода - окно Output, консоль, файл, - содержащие одну и ту же информацию.

Три канала вывода

увеличить изображение
Рис. 10.2. Три канала вывода

Метод Флойда и утверждения Assert

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

Одним из методов доказательства правильности программ был метод Флойда, при котором программа разбивалась на участки, окаймленные утверждениями - булевскими выражениями (предикатами). Истинность начального предиката должна была следовать из входных данных программы. Затем для каждого участка доказывалось, что из истинности предиката, стоящего в начале участка, после завершения выполнения соответствующего участка программы гарантируется истинность следующего утверждения - предиката в конце участка. Конечный предикат описывал постусловие программы.

Схема Флойда используется на практике, по крайней мере, программистами, имеющими вкус к строгим методам доказательства. Утверждения становятся частью программного текста. Само доказательство может и не проводиться: чаще всего у программиста есть уверенность в справедливости расставленных утверждений и убежденность, что при желании он мог бы провести и строгое доказательство. В C# эта схема поддерживается тем, что классы Debug и Trace имеют метод Assert, аргументом которого является утверждение. Что происходит, когда вычисление достигает соответствующей точки и вызывается метод Assert? Если истинно булево выражение в Assert, то это означает, что все в порядке, требуемые условия соблюдены, и вычисления продолжаются, не оказывая никакого влияния на нормальный ход вычислений. Если оно ложно, то корректность вычислений под сомнением, их выполнение приостанавливается и появляется окно с уведомлением о произошедшем событии.

На рис. 10.3 показано, как выглядит окно Assert, появившееся в ходе выполнения примера, который рассматривается ниже.

Нарушение утверждения Assert

увеличить изображение
Рис. 10.3. Нарушение утверждения Assert

В этой ситуации у программиста есть несколько возможностей:

  • прервать выполнение, нажав кнопку Abort ;
  • перейти в режим отладки ( Retry );
  • продолжить вычисления, проигнорировав уведомление.

В последнем случае сообщение о возникшей ошибке будет послано всем слушателям коллекции TraceListenerCollection.

Рассмотрим пример, демонстрирующий утверждение Assert:

public void TestAssert()
  {
      Random rnd = new Random();
      Stream myFile = new FileStream("TestFile.txt", 
                                     FileMode.Create, FileAccess.Write);
      TextWriterTraceListener myTextListener = new
          TextWriterTraceListener(myFile);
      int y = Debug.Listeners.Add(myTextListener);
      TextWriterTraceListener myWriter = new
          TextWriterTraceListener(System.Console.Out);
      Trace.Listeners.Add(myWriter);
      Trace.AutoFlush = true;
      Trace.WriteLine("автоматический вывод из буфера:" +
          Trace.AutoFlush);

      //Случайный выбор
      int u = rnd.Next(2, 12), v = rnd.Next(2, 12),
          w = rnd.Next(2, 12);
      //Спорное утверждение
      Trace.Assert(u + v + w <= 21, "Перебор: u + v + w > 21");

      Debug.WriteLine("u = " + u + " v = " + v + " w = " + w); 
      myWriter.WriteLine("Вывод только на консоль");
      Trace.Flush();
      //Вывод только в файл
      byte[] buf = { (byte)'B', (byte)'u', (byte)'f' };
      myFile.Write(buf, 0, 3);
      myFile.Close();
  }

Как и в предыдущем примере, здесь создаются два слушателя, направляющие вывод отладочных сообщений на консоль и в файл. Когда произошло нарушение утверждения Assert, оно было проигнорировано, но сообщение о нем автоматически было направлено всем слушателям. Метод также демонстрирует возможность параллельной работы с консолью и файлом. На рис. 10.4 показаны результаты записи в файл.

Файл с записью сообщения о нарушении утверждения Assert

Рис. 10.4. Файл с записью сообщения о нарушении утверждения Assert

Вариацией метода Assert является метод Fail, приводящий к появлению окна с сообщением о нарушении утверждения и отправке сообщения слушателям. Проверку утверждения программист выполняет обычным путем, например, в операторе if, и в случае нарушений вызывает метод Fail.

Классы StackTrace и BooleanSwitch

В библиотеке FCL имеются и другие классы, полезные при отладке. Класс StackTrace позволяет получить программный доступ к стеку вызовов. Класс BooleanSwitch предоставляет механизм, аналогичный константам условной компиляции. Он разрешает определять константы, используемые позже в методе условной печати WriteIf классов Debug и Trace. Мощь этого механизма в том, что константы можно менять в файле конфигурации проекта, не изменяя код проекта и не требуя его перекомпиляции.

Отладка и инструментальная среда Visual Studio .Net

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

< Лекция 10 || Лекция 11: 123456
Федор Антонов
Федор Антонов

Здравствуйте!

Записался на ваш курс, но не понимаю как произвести оплату.

Надо ли писать заявление и, если да, то куда отправлять?

как я получу диплом о профессиональной переподготовке?

Илья Ардов
Илья Ардов

Добрый день!

Я записан на программу. Куда высылать договор и диплом?