Отправляет email-рассылки с помощью сервиса Sendsay

.NET: Записки программиста

  Все выпуски  

.NET: Записки программиста или хлопок одной ладони . Работа над ошибками (окончание).


Информационный Канал Subscribe.Ru

.NET: Записки программиста или хлопок одной ладони


Выпуск второй: Работа над ошибками (окончание) 


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

В этом выпуске я обещал показать на реальном примере, как создавать инфраструктуру для обработки ошибок в приложениях.

Вкратце напомню о чем шла речь в прошлой рассылке: фирма Microsoft разработала библиотеку Enterprise Library , состоящую из application blocks, которые позволяют решать многие стандартные задачи, в том числе и управлять обработкой ошибок. Я рассказывал о первой версии этой библиотеки, которой пользовался до недавнего времени. Сейчас существует вторая версия, последний релиз которой вышел в июне 2005 года. Поскольку рассказывать о чем-то на примере уже недоступного кода как-то глупо, я вздохнул, скачал последнюю версию и подумал "ну, нет худа без добра, давно пора было разобраться, а тут глядишь, пока объясняешь - может и сам чего-то поймешь ...". Итак, Exception Handling Application Block.

Сначала об отличиях:

1. В первой версии Exception Management Application Block был независим и полагался только на себя. Сейчас он работает в связке с Logging and Instrumentation Application Block для логирования сообщений об ошибках и с Configuration Application Block для чтения конфигурационной информации.

2. Усложнилась логика обработки ошибок: сейчас с одним событием (возникновением исключения) Вы можете связать сразу несколько обработчиков, которые будут вполняться последовательно.

3. Усложнились и сами обработчики. Раньше Вы говорили "Вот исключение - опубликуй его" и обработчик просто сохранял его в какое-нибудь хранилище (Windows event log, текстовый файл, базу данных). Сейчас же Вы можете создать одну или несколько политик (policy), указав, какие типы исключений они будет обрабатывать. С каждой из политик вы связываете один или несколько обработчиков. Существует три стандартных типа обработчиков:

  • Logging Handler - для публикации в Event log
  • Replace Handler - заменяет обрабатываемое исключение другим
  • Wrap Handler - оборачивает обрабатываемое исключение другим исключением

Так же можно создавать свои обработчики (Custom Handler), которые могут выполнять более полезные действия, например отправлять сообщения об ошибках вам по почте.

4. Еще одно усовершенствование не было описано в списке новинок, но для такого лентяя как я заняло твердое первое место: визуальный редактор для конфигурирования настроек. Теперь config файл приложения можно открыть при помощи специальной утилиты и выполнить все настройки без копирования тегов и вспоминания, какой же атрибут позволяет сделать то-то и то-то.

 

Инфраструктура для обработки ошибок в приложениях

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

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

2. Сообщать пользователю об ошибке, используя общие предложения типа "В приложении произошел сбой. За справками обращайтесь в нашу службу поддержки."

3. Сохранять описания ошибок (и возможно извещать о них разработчиков)

Для того чтобы этого достигнуть, постарайтесь забыть о кодах возврата, методах GetLastError() и прочих приемах времен 3"-дюймовых дискет и 14"-дюймовых мониторов. Если у Вас произошла ошибка - Вы генерируете исключение. Точка. Тогда весь код по обработке ошибок можно сосредоточить в одном месте и не заботиться о том, как именно справиться с конкретной возникшей ситуацией. Конечно, в реальной жизни все немного сложнее. Возможно Вам прийдется перехватывать ошибки чтобы восстановить ваше окружение (например откатить транзакцию). Или же Вы разрабатываете распределенное многоуровневое приложение, тогда желательно чтобы каждый уровень сам обрабатывал свои исключения, добавляя туда осмысленную информацию.

Например:
У Вас типичное трехуровневое приложение: презентационный урвень, бизнес объекты и база данных. Конечно можно генерировать исключения и не обрабатывать их на каждом уровне, но тогда при ошибке в базе данных это исключение без изменений дойдет до презентационного уровня полностью потеряв смысл сообщения. Иными словами, Вы нажали на кнопку "Поиск" и увидели сообщение "Error of constraint PK_Single_Item: data missing." Какой триггер? Какие данные? На своем уровне я работаю в терминах "турфирма", "поиск" и хочу видеть сообщения этого же уровня, например "Ошибка поиска: некорректно сформулирован запрос." Для этого желательно перехватывать исключения на каждом уровне и оборачивать их более осмысленными сообщениями. Тогда они остаются понятными, а по стеку исключений (Stack trace) можно восстановить весь путь, пройденный этим сообщением.

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

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

 

Итак, exception handling Quick Start ...

Мы рассмотрим обработку ошибок на примере Web-приложения. Они по своей сути имеют больше особенностей и если Вы реализовали какой-то алгоритм под Web, переписать это как Windows приложение получается проще и никак не наоборот (если конечно Вашей задачей не было отображение html :).

Итак:

1. Открываем MS Visual Studio 2003 и создаем там новое web-приложение.

2. Помещаем на форму кнопку и добавляем обработчик события "Click", на котором мы и будем упражняться:

private void Button1_Click(object sender, EventArgs e)
{
    throw new Exception("Test exception.");
}

В нем мы симитировали возникновение ошибки. Неважно, произошла она в этом методе или пришла по цепочке откуда-то из глубин бизнес логики или базы данных. Теперь, при нажатии кнопки мы получим "желтый экран" (для ASP это является аналогом "blue screen" - синего экрана смерти в Windows 2000 :)

Теперь добавим обработку ошибок используя Exception Handling Application Block.

3. Добавим к приложению ссылки на необходимые библиотеки. В Solution Explorer выбираем команду "Add reference", нажимаем "Browse...", переходим в каталог, в который Вы проинсталлировали Enterprise Library (по умолчанию это "C:\Program Files\Microsoft Enterprise Library June 2005\bin") и выбираем "Microsoft.Practices.EnterpriseLibrary.ExceptionHandling" и "Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging".

Если Вы при этом подумали "Упсс,ее же еще установить нужно!" - самое время это сделать. Загрузить библиотеку можно по ссылке Enterprise Library Download.

4. Теперь нам нужно создать политики по обработке ошибок. Мы зададим самый распространенный вариант - просто сохранить описание ошибки. Для этого запустите утилиту "Enterprise Library Configuration" (она идет в поставке Enterprise Library и должна быть доступна в меню "All Programs \ Microsoft patterns & practices \ Enterprise Library - June 2005 \ Enterprise Library Configuration"). Откройте конфигурационный файл web-приложения "web.config" (команда "Open" утилиты). В TreeView слева должен появиться пустой узел "Application" - это Ваше прилолжение.

В контекстном меню для этого уза выберите команду "New \ Exception Handling Application Block". К узлу "Application" добавятся два подчиненных узла "Configuration Application Block" (настройки для чтения конфигурационного файла) и "Exception Handling Application Block".

В контекстном меню для узла "Exception Handling Application Block" выбираем команду "New \ Exception Policy". Под ним появится подчиненный узел "Exception Policy".

Теперь мы должны задать, к каким типам исключений будет относится эта политика. В контекстном меню для этого узла выбираем "New \ Exception Type" и в появившемся диалоге "Type Selector" выбираем узел "Exception". Это базовый тип для всех исключений, так что созданная нами политика будет относится ко всем исключениям без исключений (простите за невольный каламбур).

Под узлом "Exception Policy" появится узел "Exception". В правой части экрана установите значение "PostHandlingAction" в "None". Это значит, что после обработки исключения больше ничего не произойдет. Другие доступные варианты:

  • NotifyRethrow - указывает коду, который вызвал обработчик, что после его завершения рекомендуется выбросить исключение дальше (throw оператор)
  • ThrowNewException - после завершения обработки это исключение будет выброшено дальше самим обработчиком

Теперь осталось указать обработчики, которые будут вызываться для выбранного типа исключений. В контекстном меню для узла "Exception" выберите "New \ Logging Handler". Мы добавили обработчик, который будет сохранять описание ошибки в Windows Event Log. Другие доступные обработчики:

  • Replace Handler - заменяет одно исключение другим
  • Wrap Handler - оборачивает исключение в другое исключение (исходное исключение доступно по свойству "InnerException" нового исключения)
  • Custom Handler - свой собственный обработчик

При этом к узлу "Application" добавился еще один узел "Logging and Instrumentation Application Block" с описанием настроек для сохранения(логирования) ошибок.

И последнее, для узла "Logging Handler" в правой части экрана установите свойство "LogCategory" в "General" (иначе утилита сообщит, что это значение не может оставаться неопределенным).

5. Добавим объявление Exception Handling блока в WebForm1.cs файл (конечно, в реальных приложениях такиt именf встречаться не должнs, но для простоты я оставил все назвния такими, какими их создал генератор Visual Studio):

using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling;

и изменим код нашей функции на:

private voidButton1_Click(object sender, EventArgs e)
{
    try
    {
        throw new Exception("Test exception.");
    }
    catch (Exception ex)
    {
        if ( ExceptionPolicy.HandleException(ex, "Exception Policy") )
        {
            throw ;
        }
    }
}

Как видите, мы передаем полученное исключение методу "HandleException" - главной точке вход в Exception Handling Application Block и указываем, что нужно использовать политику "Exception policy" для его обработки. Метод вернет "true", если политика рекомендует выбросить это исключение дальше (помните, мы задавали свойство "PostHandlingAction"?).

Теперь при запуске приложения после нажатия кнопки, описание ошибки будет сохранено в Windows Event Log, а обработка исключения на этом завершиться (т.е. "желтого экрана" мы уже не увидим).

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

  • Добавить свои обработчики ошибок ("Custom Handler")
  • Вынести код для обработки ошибок в базовый класс для всех страниц вашего приложения

В своей практике я использую два дополнительных обработчика. Первый, сохраняет описания в текстовом файле, так как на этапе разработки обращаться к нему намного удобнее чем к Event Log, особенно если приложение запущено на другом компьютере (что характерно для web-приложений). Второй, отсылает описание ошибок по почте - тогда Вы сразу будете знать, что с вашим приложением что-то не так, это опять таки очень удобно для Web-прилжений.

Для создания своего обработчика Вам нужно создать класс, унаследованный от класса ExceptionHandler и переопределить метод HandleException. Enterprise Library содержит примеры использования блоков, в одном из них - "ExceptionHandlingWithLoggingQuickStart" есть пример такого класса "AppMessageExceptionHandler", который выводит описание ошибки во всплывающем окне. Выглядит это так:

/// <summary>
/// Summary description for GlobalPolicyExceptionHandler.
/// </summary>
public class AppMessageExceptionHandler : ExceptionHandler
{
    public AppMessageExceptionHandler() { }

    public override void Initialize(ConfigurationView configurationView) { }

    public override Exception HandleException(Exception exception, string policyName, Guid correlationID)
    {
        DialogResult result = this.ShowThreadExceptionDialog(exception);< BR >
        // Exits the program when the user clicks Abort.
        if (result == DialogResult.Abort)
            Application.Exit();< BR >
        return exception;
    }

    // Creates the error message and displays it.
    private DialogResult ShowThreadExceptionDialog(Exception e)
    {
        string errorMsg = e.Message + Environment.NewLine + Environment.NewLine;< BR >
        return MessageBox.Show(errorMsg, "Application Error", MessageBoxButtons.OK, MessageBoxIcon.Stop);
    }
}

Так же бывает удобно вынести код для обработки ошибок в базовый класс. Если у Вас уже есть базовый класс для всех Ваших страниц - поместите код туда, если нет - это удачный повод его создать. Он будет унаследован от класса System.Web.UI.Page или одного из его производных классов. Поскольку практически весь код, который добавляют разработчики, создается в виде обработчиков событий типа Init, Load, PreRender, а так же событий подчиненных элементов управления, принадлежащих форме, у класса Page достаточно переопределить виртуальные методы:

  • OnInit - генерирует событие Init класса Page
  • OnLoad - генерирует событие Load класса Page
  • OnPreRender - генерирует событие PreRender класса Page
  • RaisePostBackEvent - в этом методе генерируются все события элементов управления, принадлежащих форме (например "TextChanged" у TextBox)

Например:

/// <summary>
/// Notifies the server control that caused the postback that it should handle an incoming post back event.
/// </summary>
protected override void RaisePostBackEvent(IPostBackEventHandler sourceControl, string eventArgument)
{
    // Enclose call of this method to catch all exceptions that can be raised during base executing.
    try
    {
        base .RaisePostBackEvent(sourceControl, eventArgument);
    }
    catch (Exception ex)
    {
        if ( ExceptionPolicy.HandleException(ex, "Exception Policy") )
        {
            throw ;
        }
    }
}

Естественно, если Вы переопределяете какие-либо другие виртуальные методы класса Page, обработку исключений нужно добавлять и туда.

На этом пожалуй все, приятной работы и до следующего выпуска!


Subscribe.Ru
Поддержка подписчиков
Другие рассылки этой тематики
Другие рассылки этого автора
Подписан адрес:
Код этой рассылки: comp.soft.prog.prgnotes
Отписаться
Вспомнить пароль

В избранное