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

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

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

Корректность класса

Обобщим понятие корректности на класс. Пусть C - это класс с полями X = {x1, x2, …xn) и набором методов M = {M1, M2, …Mk). Спецификации класса зададим как набор предусловий и постусловий для каждого метода класса и набора предикатов P = {P1, P2, …Ps}, заданных на полях класса.

Определение 6 ( корректности класса ). Класс C корректен по отношению к своим спецификациям, если:

  1. все методы класса корректны по отношению к своим предусловиям и постусловиям;
  2. для всех конструкторов класса в роли постусловия выступают предикаты Pi, называемые инвариантами класса. Это означает истинность триады: {Pre} Ctor {P1 & P2 & … &Ps} Конструкторы обеспечивают начальную истинность инвариантов класса;
  3. каждый метод класса сохраняет истинность инвариантов класса: {Pre & P1 & P2 & … & Ps} M { P1 & P2 & …& Ps}

Это определение учитывает тот факт, что на полях класса могут быть заданы определенные соотношения. Инварианты класса отражают связи между полями. Например, для класса " Банковский счет " с полями " приход ", " расход " и " баланс " должны выполняться соотношения:

баланс = приход - расход
баланс > С0

Искусство отладки

Нужно стараться создавать надежный код. Но без отладки пока обойтись невозможно. Роль тестеров в современном процессе разработки ПО велика.

Отладка это некоторый детективный процесс. Программа, в которую внесены изменения, подозревается в том, что она работает некорректно. Презумпция невиновности здесь не применима. Если удается предъявить тест, на котором программа дает неверный результат, то доказано, что подозрения верны. Втайне мы всегда надеемся, что программа работает правильно. Но цель тестирования другая, попытаться опровергнуть это предположение. Отладка может доказать некорректность программы, но она не может доказать ее правильность. Отладка не гарантирует корректности программы, даже если все тесты прошли успешно. Искусное тестирование создает высокую степень уверенности в корректности программы.

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

Как и во всякой детективной истории, в ходе отладки необходим сбор улик, для чего применяется две группы средств. Первая позволяет контролировать ход вычислительного процесса: порядок следования операторов в методах, порядок вызова самих методов, условия окончания циклов, правильность переходов. Вторая отслеживает изменение состояния вычислительного процесса (значения свойств объектов) в процессе выполнения.

Отладочная печать и условная компиляция

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

Хотелось бы иметь легкий механизм управления отладочными методами, позволяющий включать при необходимости те или иные инструменты. Для этого можно воспользоваться механизмом условной компиляции, встроенным в язык C#. Этот механизм состоит из двух частей. К проекту, точнее, к конфигурации проекта можно добавить специальные константы условной компиляции. Вызов отладочного метода может быть сделан условным. Если соответствующая константа компиляции определена, то происходит компиляция вызова метода, и он будет вызываться при выполнении проекта. Если же константа не определена (выключена), то вызов метода не будет компилироваться и никаких динамических проверок - вызывать метод или нет - делаться не будет.

Как задавать константы компиляции? Напомню, что проекты в Visual Studio существуют в нескольких конфигурациях. В ходе работы с проектом можно легко переключаться с одной конфигурации на другую, после чего она становится активной, можно изменять настройки конфигурации, можно создать собственные конфигурации проекта. По умолчанию проект создается в двух конфигурациях - Debug и Release, первая из которых предназначена для отладки, вторая - для окончательных вычислений. Первая не предполагает оптимизации, и в ней определены две константы условной компиляции - DEBUG и TRACE, во второй - определена только константа TRACE. Отладочная версия может содержать вызовы, зависящие от константы DEBUG, которые будут отсутствовать в финальной версии. Используя страницу свойств, к конфигурации проекта можно добавлять новые константы компиляции.

Можно также задавать константы условной компиляции в начале модуля проекта вперемешку с предложениями using. Предложение define позволяет определить новую константу:

#define COMPLEX

Как используются константы условной компиляции? В языке 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.

Трассировка вычислений в процессе отладки

Рис. 10.1. Трассировка вычислений в процессе отладки

При переходе к конфигурации Release отладочная информация появляться не будет.

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

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

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

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

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

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

Добрый день!

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