Введение в, программирование, язык программирования, основы, операционная система
учебники, программирование, основы, введение в,

 

Отладка и обработка исключительных ситуаций

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


В лекции 9 введено строгое понятие корректности метода по отношению к его спецификациям, заданным в виде предусловия и постусловия метода.

Корректность - это способность программной системы работать в строгом соответствии со своей спецификацией. Отладка - процесс, направленный на достижение корректности.
Во время работы системы могут возникать ситуации, выходящие за пределы, предусмотренные спецификацией. Такие ситуации называются исключительными. Устойчивость - это способность программной системы должным образом реагировать на исключительные ситуации. Обработка исключительных ситуаций - процесс, направленный на достижение устойчивости.
Почему так трудно создавать корректные и устойчивые программные системы? Все дело в сложности разрабатываемых систем. Когда в 60-х годах прошлого века фирмой IBM создавалась операционная система OS-360, то на ее создание потребовалось 5000 человеко-лет, и проект по сложности сравнивался с проектом высадки первого человека на Луну. Сложность нынешних сетевых операционных систем, систем управления хранилищами данных, прикладных систем программирования на порядки превосходит сложность OS-360, так что, несмотря на прогресс, достигнутый в области технологии программирования, проблемы, стоящие перед разработчиками, не стали проще.
Жизненный цикл программной системы
Под "жизненным циклом" понимается период от замысла программного продукта до его "кончины". Обычно рассматриваются следующие фазы этого процесса:
Проектирование у Разработка уРазвертывание и Сопровождение
Все это называется циклом, поскольку после каждой фазы возможен возврат к предыдущим этапам. В объектной технологии этот процесс является бесшовным, все этапы которого тесно переплетены. Не следует рассматривать его как однонаправленный - от проектирования к сопровождению. Чаще всего, ситуация обратная: уже существующая реализация системы, прошедшая сопровождение, и существующие библиотеки компонентов оказывают решающее влияние на то, какой будет новая система, каковы будут ее спецификации.
Вот некоторые типовые правила, характерные для процесса разработки ПО:

  • Уделяйте этапу проектирования самое пристальное внимание. Успех дела во многом определяется первым этапом. Нет смысла торопиться с переходом на последующие этапы, пока не составлены ясные и четкие спецификации. Ошибки этого этапа - самые дорогие и трудно исправляемые.
  • Помните о тех, для кого разрабатывается программный продукт. Идите "в люди", чтобы понять, что нужно делать. Вместе с тем, не следует полностью полагаться на пользователей - их опыт консервативен, новые идеи могут часто приходить от разработчиков, а не от пользователей.
  • Разработка не начинается "с нуля". Только используя уже готовые компоненты, можно своевременно создать новую систему. Работая над проектом, думайте о будущем, Не создавайте компоненты, допускающие их повторное использование в других проектах.
  • Создавайте как можно раньше прототип свой системы и передавайте его пользователям в опытную эксплуатацию. Это поможет устранить множество недостатков и ошибок в заключительной версии программного продукта.
  • Какие бы хорошие спецификации не были написаны, какими бы хорошими технологиями и инструментами не пользовались разработчики, какими бы профессионалами они ни были - этого еще не достаточно для успеха дела. Необходимым условием является управление проектом, наличие специальных средств управления. Но и этого не достаточно. Третьим важным фактором является существование команды. Коллектив разработчиков должен представлять собой единый коллектив. Умение работать в команде так же важно, как и профессиональные навыки разработчика.

Три закона программотехники
Первый закон (закон для разработчика)
Корректность системы - недостижима. Каждая последняя найденная ошибка является предпоследней.
Этот закон отражает сложность нетривиальных систем. Разработчик всегда должен быть готов к тому, что в работающей системе имеются ситуации, в которых система работает не в точном соответствии со своей спецификацией, так что от него может требоваться очередное изменение либо системы, либо ее спецификации.
Второй закон (закон для пользователя)
Не бывает некорректных систем. Каждая появляющаяся ошибка при эксплуатации системы - это следствие незнания спецификации системы.
Есть два объяснения справедливости второго закона. Несерьезное объяснение состоит в том, что любая система, что бы она ни делала, при любом постусловии корректна по отношению к предусловию False, поскольку невозможно подобрать ни один набор входных данных, удовлетворяющих этому предусловию. Так что все системы корректны, если задать False в качестве их предусловия. Если вам пришлось столкнуться с системой, предусловие которой близко к False, то лучшее, что можно сделать, это отложить ее в сторону и найти другую.
Более поучительна реальная ситуация, подтверждающая второй закон и рассказанная мне в былые годы Виталием Кауфманом - специалистом по тестированию трансляторов. В одной серьезной организации была разработана серьезная прикладная система, имеющая для них большое значение. К сожалению, при ее эксплуатации сплошь и рядом возникали ошибки, из-за которых организация вынуждена была отказаться от использования системы. Разработчики обратились к нему за помощью. Он, исследуя систему, не внес в нее ни строчки кода. Единственное, что он сделал, это описал точную спецификацию системы, благодаря чему стала возможной нормальная эксплуатация.
Обратите внимание на философию, характерную для этих законов: при возникновении ошибки разработчик и пользователь должны винить себя, а не кивать друг на друга. Так что часто встречающиеся фразы "Ох уж эта фирма Чейтософт - вечно у них ошибки!" характеризует, мягко говоря, непрофессионализм говорящего.
Третий закон (закон чечако)
Если спецификацию можно нарушить, - она будет нарушена. Новичок (чечако) способен "подвесить" любую систему.
Неквалифицированный пользователь в любом контексте всегда способен выбрать наименее подходящее действие, явно не удовлетворяющее спецификации, которая ориентирована на "разумное" поведение пользователей. Полезным практическим следствием этого закона является привлечение к этапу тестирования системы неквалифицированного пользователя - "человека с улицы".
Отладка
Что должно делать для создания корректного и устойчивого программного продукта? Как минимум, необходимо:

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

Создание надежного кода
Большинство вопросов, затрагиваемых в этой лекции, в том числе и проблемы создания надежного кода, заслуживают отдельного и глубокого рассмотрения. К сожалению, придется ограничиться лишь высказыванием ряда тезисов.
Для повышения надежности нужно уменьшить сложность системы, и главное в этом процессе - это повторное использование. В идеале большая часть системы должна быть собрана из уже готовых компонентов. Объектная технология проектирования вносит свой вклад в повышение надежности кода. Наследование и универсализация позволяют, не изменяя уже существующие классы, создать новые классы, новые типы данных, придающие проектируемой системе новые свойства при минимальных добавлениях нового кода. Статический контроль типов позволяет выявить многие ошибки еще на этапе компиляции. Динамическое связывание и полиморфизм позволяют автоматически включать объекты классов-потомков в уже существующие схемы работы - методы родителя могут вызывать методы потомков, ничего не зная о появлении этих новых потомков. Автоматическая сборка мусора позволяет снять с разработчика обязанности управления освобождением памяти и предотвратить появление крайне неприятных и опасных ошибок, связанных с некорректным удалением объектов.
Крайне важную роль в создании надежного кода играют спецификации методов класса, класса в целом, системы классов. Спецификации являются частью документации, встроенной в проект, и вообще важной его частью. Их существование облегчает не только создание корректного кода, соответствующего спецификации, но и создание системы тестов, проверяющих корректность кода. Нужно сказать, что существуют специальные инструментальные средства, поддерживающие автоматическое создание тестов на основе спецификаций. Незаменима роль спецификаций на этапе сопровождения и повторного использования компонентов. Невозможно повторно использовать компонент, если у него нет ясной и полной спецификации.

http://localhost:3232/img/empty.gifhttp://localhost:3232/img/empty.gifИскусство отладки

Нужно стараться создавать надежный код. Но без отладки пока обойтись невозможно. Роль тестеров в современном процессе разработки ПО велика.
Отладка - это некоторый детективный процесс. Программа, в которую внесены изменения, подозревается в том, что она работает некорректно. Презумпция невиновности здесь не применима. Если удается предъявить тест, на котором программа дает неверный результат, то доказано, что подозрения верны. Втайне мы всегда надеемся, что программа работает правильно. Но цель тестирования другая - попытаться опровергнуть это предположение. Отладка может доказать некорректность программы, но она не может доказать ее правильность. Отладка не гарантирует корректности программы, даже если все тесты пройдены успешно. Искусное тестирование создает высокую степень уверенности в корректности программы.
Часть ошибок программы ловится автоматически еще на этапе компиляции. Сюда относятся все синтаксические ошибки, ошибки несоответствия типов и некоторые другие. Это простые ошибки и их исправление, как правило, не вызывает трудностей. В отладке нуждается синтаксически корректная программа, результаты вычислений которой получены, но не соответствуют требуемым спецификациям. Чаще всего еще не отлаженная программа на одних исходных данных работает правильно, на других - дает ошибочный результат. Искусство отладки состоит в том, чтобы обнаружить все ситуации, в которых работа программы приводит к ошибочным вычислениям.
Как и во всякой детективной деятельности, в ходе отладки необходим сбор улик, для чего применяется две группы средств. Первая позволяет контролировать ход вычислительного процесса: порядок следования операторов в методах, порядок вызова самих методов, условия окончания циклов, правильность переходов. Вторая отслеживает изменение состояния вычислительного процесса (значения свойств объектов) в процессе выполнения.
Есть и другая классификация. Средства, используемые при отладке, можно разделить на инструментарий, предоставляемый средой разработки Visual Studio .Net, и программные средства, предоставляемые языком и специальными классами библиотеки FCL. Начнем рассмотрение с программных средств.
Отладочная печать и условная компиляция
Одним из основных средств отладки является отладочная печать, позволяющая получить данные о ходе и состоянии процесса вычислений. Обычно разрабатываются специальные отладочные методы, вызываемые в критических точках программы - на входе и выходе программных модулей, на входе и выходе циклов и так далее. Искусство отладки в том и состоит, чтобы получить нужную информацию о прячущихся ошибках, проявляющихся, возможно, только в редких ситуациях.
Хотелось бы иметь легкий механизм управления отладочными методами, позволяющий включать при необходимости те или иные инструменты. Для этого можно воспользоваться механизмом условной компиляции, встроенным в язык C#. Этот механизм состоит из двух частей. К проекту, точнее, к конфигурации проекта можно добавить специальные константы условной компиляции. Вызов отладочного метода может быть сделан условным. Если соответствующая константа компиляции определена, то происходит компиляция вызова метода и он будет вызываться при выполнении проекта. Если же константа не определена (выключена), то вызов метода даже не будет компилироваться и никаких динамических проверок - вызывать метод или нет - делаться не будет.
Как задавать константы компиляции? Напомню, что проекты в Visual Studio существуют в нескольких конфигурациях. В ходе работы с проектом можно легко переключаться с одной конфигурации на другую, после чего она становится активной, можно изменять настройки конфигурации, можно создать собственные конфигурации проекта. По умолчанию проект создается в двух конфигурациях - Debug и Release, первая из которых предназначена для отладки, вторая - для окончательных вычислений. Первая не предполагает оптимизации и в ней определены две константы условной компиляции - DEBUG и TRACE, во второй - определена только константа TRACE. Отладочная версия может содержать вызовы, зависящие от константы DEBUG, которые будут отсутствовать в финальной версии. Используя страницу свойств, к конфигурации проекта можно добавлять новые константы компиляции.


В лекции 2 рассказывалось, как добраться до страницы свойств проекта. Взгляните еще раз на рис. 2.3 этой лекции, где показана страница свойств, и обратите внимание на первую строчку, содержащую список констант условной компиляции активной конфигурации (в данном случае - 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.
При переходе к конфигурации Release отладочная информация появляться не будет.

http://localhost:3232/img/empty.gifhttp://localhost:3232/img/empty.gifКлассы 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, попадают они в одну коллекцию. Как и обещано, один из этих слушателей направляет вывод в файл, другой на консоль. На рис. 23.2 на фоне окна кода показаны три канала вывода - окно Output, консоль, файл - содержащие одну и ту же информацию.
Метод Флойда и утверждения Assert
Лет двадцать назад большие надежды возлагались на формальные методы доказательства правильности программ, позволяющие доказывать корректность программ аналогично доказательству теорем. Реальные успехи формальных доказательств невелики. Построение такого доказательства не проще написания корректной программы, а ошибки столь же возможны и часты, как и ошибки программирования. Тем не менее, эти методы оказали серьезное влияние на культуру проектирования корректных программ, появление в практике программирования понятий предусловия и постусловия, инвариантов и других важных понятий.
Одним из методов доказательства правильности программ был метод Флойда, при котором программа разбивалась на участки, окаймленные утверждениями - булевскими выражениями (предикатами). Истинность начального предиката должна была следовать из входных данных программы. Затем для каждого участка доказывалось, что из истинности предиката, стоящего в начале участка, после завершения выполнения соответствующего участка программы гарантируется истинность следующего утверждения - предиката в конце участка. Конечный предикат описывал постусловие программы.
Схема Флойда используется на практике, по крайней мере, программистами, имеющими вкус к строгим методам доказательства. Утверждения становятся частью программного текста. Само доказательство может и не проводиться: чаще всего у программиста есть уверенность в справедливости расставленных утверждений и убежденность, что при желании он мог бы провести и строгое доказательство. В C# эта схема поддерживается тем, что классы Debug и Trace имеют метод Assert, аргументом которого является утверждение. Что происходит, когда вычисление достигает соответствующей точки и вызывается метод Assert? Если истинно булево выражение в Assert, то вычисления продолжаются, не оказывая никакого влияния на нормальный ход вычислений. Если оно ложно, то корректность вычислений под сомнением, их выполнение приостанавливается и появляется окно с уведомлением о произошедшем событии, что показано на рис. 23.3.
В этой ситуации у программиста есть несколько возможностей:

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

В последнем случае сообщение о возникшей ошибке будет послано всем слушателям коллекции TraceListenerCollection.
Рассмотрим простой пример, демонстрирующий нарушение утверждения:
public void WriteToFile()
{
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 x = 22;
Trace.Assert(x<=21, "Перебор");
myWriter.WriteLine("Вывод только на консоль");
//Trace.Flush();
//Вывод только в файл
byte[] buf = {(byte)'B',(byte)'y'};
myFile.Write(buf,0, 2);                   
myFile.Close();
}
Как и в предыдущем примере, здесь создаются два слушателя, направляющие вывод отладочных сообщений на консоль и в файл. Когда произошло нарушение утверждения Assert, оно было проигнорировано, но сообщение о нем автоматически было направлено всем слушателям. Метод также демонстрирует возможность параллельной работы с консолью и файлом. На рис. 23.4 показаны результаты записи в файл:
Вариацией метода Assert является метод Fail, всегда приводящий к появлению окна с сообщением о нарушении утверждения, проверка которого осуществляется обычным программным путем.

http://localhost:3232/img/empty.gifhttp://localhost:3232/img/empty.gifКлассы StackTrace и BooleanSwitch

В библиотеке FCL имеются и другие классы, полезные при отладке. Класс StackTrace позволяет получить программный доступ к стеку вызовов. Класс BooleanSwitch предоставляет механизм, аналогичный константам условной компиляции. Он разрешает определять константы, используемые позже в методе условной печати WriteIf классов Debug и Trace. Мощь этого механизма в том, что константы можно менять в файле конфигурации проекта, не изменяя код проекта и не требуя его перекомпиляции.
Отладка и инструментальная среда Visual Studio .Net
Инструментальная среда студии предоставляет программисту самый широкий спектр возможностей слежения за ходом вычислений и отслеживания состояний, в котором находится процесс вычислений. Поскольку все современные инструментальные среды организованы сходным образом и хорошо известны работающим программистам, я позволю себе не останавливаться на описании возможностей среды.
Обработка исключительных ситуаций
Какой бы надежный код ни был написан, сколь бы тщательной ни была отладка, в версии, переданной в эксплуатацию и на сопровождение, при запусках будут встречаться нарушения спецификаций. Причиной этого являются выше упомянутые законы программотехники. В системе остается последняя ошибка, находятся пользователи, не знающие спецификаций, и если спецификацию можно нарушить, то это событие когда-нибудь да произойдет. В таких исключительных ситуациях продолжение выполнения программы либо становится невозможным (попытка выполнить неразрешенную операцию деления на ноль, попытки записи в защищенную область памяти, попытка открытия несуществующего файла, попытка получить несуществующую запись базы данных), либо в возникшей ситуации применение алгоритма приведет к ошибочным результатам.
Что делать при возникновении исключительной ситуации? Конечно, всегда есть стандартный способ - сообщить о возникшей ошибке и прервать выполнение программы. Понятно, что это приемлемо лишь для безобидных приложений; даже для компьютерных игр этот способ не годится, что уж говорить о критически важных приложениях!
В языках программирования для обработки исключительных ситуаций предлагались самые разные подходы.
Обработка исключений в языках C/C++
Для стиля программирования на языке C характерно описание методов класса как булевых функций, возвращающих true в случае нормального завершения метода и false - при возникновении исключительной ситуации. Вызов метода встраивался в If-оператор, обрабатывающий ошибку в случае неуспеха завершения метода:
bool MyMethod(...){...}
if !MyMethod(){// обработка ошибки}
{//нормальное выполнение}
Недостатки этой схемы понятны. Во-первых, мало информации о причине возникновения ошибки, поэтому либо через поля класса, либо через аргументы метода нужно передавать дополнительную информацию. Во-вторых, блок обработки встраивается в каждый вызов, что приводит к раздуванию кода.
Поэтому в C/C++ применяется схема try/catch блоков, суть которой в следующем. Участок программы, в котором может возникнуть исключительная ситуация, оформляется в виде охраняемого try-блока. Если при его выполнении возникает исключительная ситуация, то происходит прерывание выполнения try-блока c классификацией исключения. Это исключение начинает обрабатывать один из catch-блоков, соответствующий типу исключения. В C/C++ применяются две такие схемы. Одна из них - схема с возобновлением - соответствует так называемым структурным, или С-исключениям. Вторая схема - без возобновления - соответствует С++ исключениям. В первой схеме обработчик исключения - catch-блок - возвращает управление в некоторую точку try-блока. Во второй схеме управление не возвращается в try-блок.
С некоторыми синтаксическими отличиями схема с возобновлением применяется в языках VB/VBA.

 
На главную | Содержание | < Назад....Вперёд >
С вопросами и предложениями можно обращаться по nicivas@bk.ru. 2013 г.Яндекс.Метрика