Сдам Сам

ПОЛЕЗНОЕ


КАТЕГОРИИ







Схема Бертрана обработки исключительных ситуаций





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

Бертран Мейер в книге [1], в которой все механизмы, используемые в объектной технологии, тщательно обосновываются, предложил следующую схему обработки исключительных ситуаций. В основе ее лежит подход к проектированию программной системы на принципах Проектирования по Контракту. Модули программной системы, вызывающие друг друга, заключают между собой контракты. Вызывающий модуль обязан обеспечить истинность предусловия, необходимого для корректной работы вызванного модуля. Вызванный модуль обязан гарантировать истинность постусловия по завершении своей работы. Если в вызванном модуле возникает исключительная ситуация, то это означает, что он не может выполнить свою часть контракта. Что должен делать обработчик исключительной ситуации? У него только две возможности - Retry и Rescue. Первая (Retry) - попытаться внести некоторые коррективы и вернуть управление охраняемому модулю, который может предпринять очередную попытку выполнить свой контракт. Модуль может, например в следующей попытке запустить другой алгоритм, использовать другой файл, другие данные. Если все закончится успешно и работа модуля будет соответствовать его постусловию, то появление исключительной ситуации можно рассматривать как временные трудности, успешно преодоленные. Если же ситуация возникает вновь и вновь, тогда обработчик события применяет вторую стратегию (Rescue), выбрасывая исключение и передавая управление вызывающему модулю, который и должен теперь попытаться исправить ситуацию. Важная тонкость в схеме, предложенной Бертраном, состоит в том, что исключение, выбрасываемое обработчиком, следует рассматривать не как панику, не как бегство, а как отход на заранее подготовленные позиции. Обработчик исключения должен позаботиться о восстановлении состояния, предшествующего вызову модуля, который привел к исключительной ситуации, и это гарантирует нахождение всей системы в корректном состоянии.

Схема Бертрана является схемой с возобновлением, и она наиболее точно описывает разумную стратегию обработки исключительных ситуаций. Не следует думать, что эта схема не может быть реализована на C#, просто она требует понимания сути и определенной структурной организации модуля. Приведу возможную реализацию такой схемы на C#:

public void Pattern(){ do { try { bool Danger = false; Success = true; MakeJob(); Danger = CheckDanger(); if (Danger) throw (new MyException()); MakeLastJob(); } catch (MyException me) { if(count > maxcount) throw(new MyException("Три попытки были безуспешны")); Success = false; count++; //корректировка ситуации Console.WriteLine("Попытка исправить ситуацию!"); level +=1; } }while (!Success);}

Приведу краткие комментарии к этой процедуре, которую можно рассматривать как некоторый образец организации обработки исключительной ситуации:

  • Конструкция try-catch блоков помещается в цикл do-while(!Success), завершаемый в случае успешной работы охраняемого блока, за чем следит булева переменная Success.
  • В данном образце предполагается, что в теле охраняемого блока анализируется возможность возникновения исключительной ситуации и, в случае обнаружения опасности, выбрасывается собственное исключение, класс которого задан программно. В соответствии с этим тело try-блока содержит вызов метода MakeJob, выполняющего некоторую часть работы, после чего вызывается метод CheckDanger, выясняющий, не возникла ли опасность нарушения спецификации и может ли работа быть продолжена. Если все нормально, то выполняется метод MakeLastJob, выполняющий заключительную часть работы. Управление вычислением достигает конца try-блока, он успешно завершается и, поскольку остается истинной переменная Success, значение true которой установлено в начале try-блока, то цикл while, окаймляющий охраняемый блок и его обработчиков исключений, также успешно завершается.
  • Если в методе CheckDanger выясняется, что нормальное продолжение вычислений невозможно, то выбрасывается исключение класса MyException. Оно перехватывается обработчиком исключения, стоящим за try-блоком, поскольку класс MyException указан как класс формального аргумента.
  • Для простоты приведен только один catch-блок. В общем случае их может быть несколько, но все они строятся по единому образцу. Предполагается, что обработчик исключения может сделать несколько попыток исправить ситуацию, после чего повторно выполняется охраняемый блок. Если же число попыток, за которым следит переменная count, превосходит максимально допустимое, то обработчик выбрасывает новое исключение, задавая дополнительную информацию и передавая тем самым обработку ошибки на следующий уровень - вызываемой программе.
  • Когда число попыток еще не исчерпано, обработчик исключения переменной Success дает значение false, гарантирующее повтор выполнения try-блока, увеличивает счетчик числа попыток и пытается исправить ситуацию.
  • Как видите, эта схема реализует два корректных исхода обработки исключительной ситуации - Retry и Rescue - повтору с надеждой выполнить обязательства и передачи управления вызывающей программе, чтобы она предприняла попытки исправления ситуации, когда вызванная программа не смогла с этим справиться.

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

Определим первым делом собственный класс исключений:

public class MyException:Exception{ public MyException() {} public MyException (string message): base(message) {} public MyException (string message, Exception e): base(message, e) {}}

Минимум того, что нужно сделать, определяя свои исключения, - это задать три конструктора класса, вызывающие соответствующие конструкторы базового класса Exception.

В классе Excepts, методом которого является наш образец Pattern, определим следующие поля класса:

Random rnd = new Random(); int level = -10; bool Success; //true - нормальное завершение int count =1; // число попыток выполнения const int maxcount =3;

Определим теперь методы, вызываемые в теле охраняемого блока:

void MakeJob(){ Console.WriteLine("Подготовительные работы завершены");}bool CheckDanger(){ //проверка качества и возможности продолжения работ int low = rnd.Next(level,10); if (low > 6) return(false); return(true);}void MakeLastJob(){ Console.WriteLine("Все работы завершены успешно");}

В классе Testing зададим метод, вызывающий метод Pattern:

public void TestPattern(){ Excepts ex1 = new Excepts(); try { ex1.Pattern(); } catch (Exception e) { Console.WriteLine("исключительная ситуация при вызове Pattern"); Console.WriteLine(e.ToString()); }}

Обратите внимание, что вызов метода Pattern находится внутри охраняемого блока. Поэтому, когда Pattern не справится с обработкой исключительной ситуации, ее обработку возьмет на себя универсальный обработчик, стоящий за try-блоком.

На рис. 23.6 показаны три варианта запуска метода TestPattern. В одном из них исключительной ситуации при вызове метода Pattern вообще не возникало, в другом - ситуация возникала, но коррекция обработчика исключения помогла и при повторе выполнения охраняемого блока в Pattern все прошло нормально. В третьем варианте метод Pattern не смог справиться с исключительной ситуацией, и она обрабатывалась в catch-блоке метода TestPattern.


Рис. 23.6. Обработка исключительных ситуаций. Три случая

Класс Exception

Рассмотрим устройство базового класса Exception, чтобы понять, какую информацию может получить обработчик исключения, когда ему передается объект, задающий текущее исключение.

Основными свойствами класса являются:

  • Message - строка, задающая причину возникновения исключения. Значение этого свойства устанавливается при вызове конструктора класса, когда создается объект, задающий исключение;
  • HelpLink - ссылка (URL) на файл, содержащий подробную справку о возможной причине возникновения исключительной ситуации и способах ее устранения;
  • InnerException - ссылка на внутреннее исключение. Когда обработчик выбрасывает новое исключение для передачи обработки на следующий уровень, то текущее исключение становится внутренним для вновь создаваемого исключения;
  • Source - имя приложения, ставшего причиной исключения;
  • StackTrace - цепочка вызовов - методы, хранящиеся в стеке вызовов в момент возникновения исключения;
  • TargetSite - метод, выбросивший исключение.

Из методов класса отметим метод GetBaseException. При подъеме по цепочке вызовов он позволяет получить исходное исключение -- первопричину возникновения последовательности выбрасываемых исключений.

Класс имеет четыре конструктора, из которых три уже упоминались. Один из них - конструктор без аргументов, второй - принимает строку, становящуюся свойством Message, третий - имеет еще один аргумент: исключение, передаваемое свойству InnerException.

В предыдущий пример я внес некоторые изменения. В частности, добавил еще один аргумент при вызове конструктора исключения в catch-блоке метода Pattern:

throw(new MyException("Все попытки Pattern безуспешны", me));

В этом случае у создаваемого исключения заполняется свойство InnerExceptions. Для слежения за свойствами исключений я добавил метод печати всех свойств, вызываемый во всех обработчиках исключений:

static public void PrintProperties(Exception e){ Console.WriteLine("Свойства исключения:"); Console.WriteLine("TargetSite = {0}", e.TargetSite); Console.WriteLine("Source = {0}", e.Source); Console.WriteLine("Message = {0}",e.Message); if (e.InnerException == null) Console.WriteLine("InnerException = null"); else Console.WriteLine("InnerException = {0}", e.InnerException.Message); Console.WriteLine("StackTrace = {0}", e.StackTrace); Console.WriteLine("GetBaseException = {0}", e.GetBaseException());}

Из-за громоздкости не привожу результаты, но отмечу, что они соответствуют описанию, приведенному в тексте лекции.

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

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

Организация интерфейса

Практически все проекты, построенные в наших лекциях, были консольными приложениями. В реальной жизни консольные проекты - это большая редкость. Причина, по которой из 12 возможных типов проектов мы выбирали наименее используемый, понятна. Нашей целью являлось изучение свойств языка, классов библиотеки FCL, для этих целей консольный проект вполне подходит, позволяя избегать введения не относящихся к сути дела деталей. Теперь цель достигнута - основные средства языка C# рассмотрены, учебный курс завершается. Остались важные темы, требующие более подробного рассмотрения, такие, как, например, работа с атрибутами, создание собственных атрибутов, класс Reflection, работа с файлами и базами данных; но все это предмет будущего курса. Тем не менее, нельзя окончить этот курс, не посвятив две последние лекции Windows-приложениям. Мне бы хотелось, чтобы активные слушатели (читатели) все консольные проекты переделали в Windows-проекты, построив подходящий для них интерфейс.

Первое знакомство с Windows-проектами состоялось в лекции 2, я настоятельно рекомендую перечитать ее, прежде чем продолжить чтение данной лекции. Вкратце напомню, как создается и выполняется Windows-проект. По умолчанию он содержит класс Form1 - наследника класса Form. Этот класс содержит точку входа в проект - процедуру Main, вызывающую статический метод Run класса Application, который создает объект класса Form1 и открывает форму - видимый образ объекта - для интерактивной работы пользователя. Открываемая форма содержит пользовательский интерфейс - окошки, кнопки, списки, другие элементы управления, меню. Все эти элементы способны реагировать на события, возникающие при выполнении пользователем каких-либо действий - нажатии кнопок, ввода текста, выбора пунктов меню.

Форма и элементы управления

Как населить форму элементами управления? Чаще всего, это делается вручную в режиме проектирования. Доступные элементы управления, отображаемые на специальной панели (Toolbox), перетаскиваются на форму. Этот процесс поддерживается особым инструментарием - дизайнером форм (Designer Form). Как только на этапе проектирования вы сажаете на форму элемент управления, немедленно в тексте класса появляются соответствующие строки кода (в лекции 2 об этом подробно рассказано). Конечно, все можно делать и программно - появление соответствующих строк кода приводит к появлению элементов управления на форме. Нужно понимать, что форма - это видимый образ класса Form, а элементы управления, размещенные на форме - это видимые образы клиентских объектов соответствующих классов, наследников класса Control. Так что форма с ее элементами управления есть прямое отражение программного кода.

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

В каких отношениях находятся класс Form, класс Control, классы элементов управления? На рис. 24.1 показана иерархия отношений, связывающих эти классы.


Рис. 24.1. Иерархия классов элементов управления

Естественно, все эти классы являются потомками прародителя - класса Object. Заметьте, класс Control в иерархии классов занимает довольно высокое положение, хотя и у него есть два важных родительских класса - класс Component, определяющий возможность элементам управления быть компонентами, и класс MarshalByRefObject, задающий возможность передачи элементов управления по сети. Класс Control задает важные свойства, методы и события, наследуемые всеми его потомками. Все классы элементов управления являются наследниками класса Control. Чаще всего, это прямые наследники, но иногда они имеют и непосредственного родителя, которым может быть абстрактный класс - это верно для кнопок, списков, текстовых элементов управления. Может показаться удивительным, но класс Form является одним из потомков класса Control, так что форма - это элемент управления со специальными свойствами. Будучи наследником классов ScrollableControl и ContainerControl, форма допускает прокрутку и размещение элементов управления.

Взаимодействие форм

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

Следует четко различать процесс создания формы - соответствующего объекта, принадлежащего классу Form или наследнику этого класса, - и процесс показа формы на экране. Для показа формы служит метод Show этого класса, вызываемый соответствующим объектом; для скрытия формы используется метод Hide. Реально методы Show и Hide изменяют свойство Visible объекта, так что вместо вызова этих методов можно менять значение этого свойства, устанавливая его либо в true, либо в false.

Заметьте разницу между сокрытием и закрытием формы - между методами Hide и Close. Первый из них делает форму невидимой, но сам объект остается живым и невредимым. Метод Close отбирает у формы ее ресурсы, делая объект отныне недоступным; вызвать метод Show после вызова метода Close невозможно, если только не создать объект заново. Открытие и показ формы всегда означает одно и то же - вызов метода Show. У формы есть метод Close, но нет метода Open. Формы, как и все объекты, создаются при вызове конструктора формы при выполнении операции new.

Форма, открываемая в процедуре Main при вызове метода Run, называется главной формой проекта. Ее закрытие приводит к закрытию всех остальных форм и завершению Windows-приложения. Завершить приложение можно и программно, вызвав в нужный момент статический метод Exit класса Application. Закрытие других форм не приводит к завершению проекта. Зачастую главная форма проекта всегда открыта, в то время как остальные формы открываются и закрываются (скрываются). Если мы хотим, чтобы в каждый текущий момент была открыта только одна форма, то нужно принять определенные меры, чтобы при закрытии (скрытии) формы открывалась другая. Иначе возможна клинчевая ситуация - все формы закрыты, предпринять ничего нельзя, а приложение не завершено. Конечно, выход всегда есть - всегда можно нажать магическую тройку клавиш CTRL+ALT+DEL и завершить любое приложение.

Можно создавать формы как объекты класса Form. Однако такие объекты довольно редки. Чаще всего создается специальный класс FormX - наследник класса Form. Так, в частности, происходит в Windows-приложении, создаваемом по умолчанию, когда создается класс Form1 - наследник класса Form. Так происходит в режиме проектирования, когда в проект добавляется новая форма с использованием пункта меню Add Windows Form. Как правило, каждая форма в проекте - это объект собственного класса. Возможна ситуация, когда вновь создаваемая форма во многом должна быть похожей на уже существующую, и тогда класс новой формы может быть сделан наследником класса формы существующей. Наследование форм мы рассмотрим подробнее чуть позже.







Что способствует осуществлению желаний? Стопроцентная, непоколебимая уверенность в своем...

Конфликты в семейной жизни. Как это изменить? Редкий брак и взаимоотношения существуют без конфликтов и напряженности. Через это проходят все...

Живите по правилу: МАЛО ЛИ ЧТО НА СВЕТЕ СУЩЕСТВУЕТ? Я неслучайно подчеркиваю, что место в голове ограничено, а информации вокруг много, и что ваше право...

Что делает отдел по эксплуатации и сопровождению ИС? Отвечает за сохранность данных (расписания копирования, копирование и пр.)...





Не нашли то, что искали? Воспользуйтесь поиском гугл на сайте:


©2015- 2024 zdamsam.ru Размещенные материалы защищены законодательством РФ.