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

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

  Все выпуски  

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


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


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

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

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

Введение (в меру философское) ...

Когда я размышлял, чему бы посвятить первые выпуски, я вспомнил одно из правил XP (eXtreme Programming): "Планируй максимально короткие итерации, чем раньше твое решение начнет работать - тем легче будет вносить изменения и тем меньше будут риски" (хочу предупредить сразу, я не являюсь горячим сторонником XP, но с удовольствием использую часть правил, которые считаю более чем разумными для подавляющего большинства проектов). Поэтому сначала мы займемся конкретными вещами и рекомендациями, а к извечным философским размышлениям а-ля "как правильно писать программы" или "что лучше: RUP или XP" вернемся несколько позже ... (последняя фраза была произнесена несколько ироничным тоном, думаю не нужно объяснять почему :)

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

ExcelFile importedAccount = User.TotalAccount.ImportToExcel();
ArchiveManager.Add(importedAccaunt);


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

На самом деле приходиться писать немного сложнее, например:

try
{
    ExcelFile importedAccount = User.TotalAccount.ImportToExcel();
    ArchiveManager.Add(importedAccaunt);
}
catch(Exception ex)
{
    string errorMessage = ResourceManager.LoadString("IDS_ET1_ACCOUNT_IMPORT_FAILED");
    errorMessage.Format(errorMessage, User.Name);
    throw new ApplicationException(errorMessage, ex);
}

Правильно, если один из методов выбросит исключение, нам будет совсем не интересно читать сообщение об ошибке типа: "File output error; acces denied." без всякого объяснения в какой момент и в каком месте это произошло. Намного приятнее будет увидеть локализованное сообщение типа "Der import of Peter Klayn is kaput: File output error; acces denied.". Ну и естественно где-то должен располагаться код, который будет обрабатывать сгенерированные исключения, выводить сообщения пользователям и сохранять описания ошибок для разработчиков.

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

Естественно, чем меньше служебного кода будет встречаться в программах - тем понятнее они будут и тем проще их будет поддерживать в дальнейшем. Самый характерный пример - именно обработка ошибок, иногда случается что на 2-3 строчки кода с пользовательской логикой приходится в два-три раза больше кода, предназначенного для обработки возможных ошибочных ситуаций. В таких случаях бывает трудно удержать себя от соблазна упростить обработку ошибок, чтобы этот код не скрывал логику приложения, но тогда может пострадать надежность, а ведь программа пишеться один раз, а поддерживается всю жизнь (хм, как-то это обреченно прозвучало, тогда переформулируем: а потом всю жизнь вы будете получать деньги за ее поддержку :).

Поэтому разработчики пытаются вынести как можно больше служебного кода за пределы программы, помещая их в специальные резервации, называемые системными библиотеками, служебными модулями и т.д. Вам наверняка знакома ситуация, когда устроившись на работу в новую фирмы Вы слышите что-нибудь типа: для того, чтобы зашифровать эти данные воспользуйся методом "Decrypt" из нашей библиотеки "VasjaPupkinAndSynovja.SuperEnterpriseLibrary.Crypto".

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


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

("Ведьмино заклятье" из цикла "Хроники смутных времен")

Ошибки в приложениях: правильный уход и обращение ...

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

Что нужно сделать с ошибкой, которая произошла и была успешно отловлена, попавшись на приманку "catch(Exception ex) "? Обычно приходится:

  • Выдать пользователю неопределенное сообщение типа "Ну-у, у нас тут небольшой сбой, ни в коем случае не пугайтесь, просто попробуйте заглянуть к нам попозже" (помните же "Является ли фраза [Приложение выполнило недопустимую ошибку и будет закрыто. Обратитесь к разработчику] официальным вызовом в США?"). Причем сообщение должно быть именно для пользователя, уже прошли те времена, когда приложения пугали пользователя замечательными по информативности всплывающими окнами "Ошибка чтения памяти по адресу 203434:AF4320. Info.Dtor(): access violation. ... ". По крайней мере я не могу припомнить ни одного пользователя, который бы сказал "А, по адресу 203434:AF4320? Так, запустим отладчик, что тут у нас ...".
  • Сохранить описание ошибки и\или известить об этом разработчиков

С первым пунктом проще, это больше идеологическая вещь и мы затронем ее несколько позже, а вот по поводу второго пункта поговорим подробнее. Итак встречайте, Exception Handling Application block из Microsoft Pattern and Practices Enterprise Library!

Примечание:
Обобщая, можно сказать что
Microsoft Pattern and Practices - это рекомендации для разработчиков, которые показывают как нужно проектировать, разрабатывать,  и распространять приложения. Эти рекомендации разрабатываются как сотрудниками Microsoft, так и фирмами-партнерами и основываются на громадном опыте разработки реальных решений. Подробнее об этом можно прочитать в Getting Started. Так же, Microsoft Pattern and Practices содержит большое количество кода в виде примеров и библиотек, основной из которые является Enterprise Library - набор application blocks, каждый из которых содержит код, инкапсулирующий какую-либо из служебных задач: обработка ошибок, кэширование данных и пр.

Сейчас нас интересует Exception Handling Application Block. В своих разработках я использовал первую версию этого блока (тогда он назывался Exception Management Application Block). Сейчас существует вторая, усовершенствованная версия (которая и называеся Exception Handling Application Block ). Чтобы случайно не ввести Вас в заблуждение, я расскажу о первом, хорошо известном мне варианте, а об отличиях второй версии Вы сможете прочитать в описании (когда я доберусь до нее сам, то обязательно расскажу об отличиях в одном из выпусков).

Итак, первая версия позволяет сформировать развернутое описание об ошибке, добавив туда некоторое количество системной информации, а так же стек вызовов функций (только в debug версии). После этого, описание передается одному или нескольким классам-публикаторам, которые сохраняют его в нужном источнике. В стандартную поставку входил публикатор для Windows Event log, кроме того можно создавать свои публикаторы (они должны реализовывать интерфейс "IExceptionPublisher"). Инсталляция Exception Management Application Block, содержала еще два примера: для публикации в текстовый файл и отсылке сообщений по почте.

Вкратце рассмотрим, как можно использовать этот application block:

1. Подключите Exception Management Application Block к проекту, используя команду "Add Reference" и выбрав библиотеки "Microsoft.ApplicationBlocks.ExceptionManagement.dll" и "Microsoft.ApplicationBlocks.ExceptionManagement.Interfaces.dll".

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

<exceptionManagement>
    <publisher assembly="Microsoft.ApplicationBlocks.ExceptionManagement" type= "Microsoft.ApplicationBlocks.ExceptionManagement.eMailExceptionPublisher" From= "{от кого отсылать письмо}" To="{кому отсылать письмо}" Subject="Exception Notification" exclude="System.Threading.ThreadAbortException" />
    <publisher assembly="Microsoft.ApplicationBlocks.ExceptionManagement" type="Microsoft.ApplicationBlocks.ExceptionManagement.TextExceptionPublisher" FileName="C:\App.log" exclude="System.Threading.ThreadAbortException" />
</exceptionManagement>

Первая из записей указывает, что для публикации ошибок следует использовать "eMailExceptionPublisher" (отсылка сообщений по почте). Кроме того, она содержит поля для формирования писем: "From", "To", "Subject" (названия самоочевидны), а так же описание, для каких ошибок не следует использовать этот публикатор. Формат описания достаточно универсален, можно указать, что этот публикатор нужно использовать только для каких-то определенных типов ошибок или для всех кроме каких-то типов (под типом ошибки здесь подразумевается имя класса-исключения).

Вторая запись описывает публикатор типа "TextExceptionPublisher" для сохранение в файле с именем "c:\app.log" (если вы разрабатываете Web-проложения, не забудте дать права на запись в этот файл для ASP.NET).

Не забудте зарегистрировать секцию <exceptionManagement>, добавив в секцию <configSections> конфигурационного файла вложенную секцию:

<section name="exceptionManagement" type="Microsoft.ApplicationBlocks.ExceptionManagement.ExceptionManagerSectionHandler, Microsoft.ApplicationBlocks.ExceptionManagement" />

3. Опубликуйте описание ошибки при помощи вызова ExceptionManager.Publish(). Ошибка будет опубликована, используя все описанные в конфигурационном файле публикаторы. В данном случае описание сохранится в текстовом файле, а так же будет отослано по почте. Это сообщение будет выглядеть примерно так:

General Information *********************************************
Additional Info: ExceptionManager.RequestedUrl: /PortalCommonModules/Html/HtmlEdit.aspx?mHtmlId=649
ExceptionManager.UserHostName: 127.0.0.1
ExceptionManager.UserHostAddress: 127.0.0.1
ExceptionManager.UrlReferrer:
http://localhost/Contact.aspx
ExceptionManager.HttpMethod: GET
ExceptionManager.UserAgent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50215)
ExceptionManager.AcceptTypes: image/gif,image/x-xbitmap,image/jpeg,image/pjpeg,application/vnd.ms-excel,application/vnd.ms-powerpoint,application/msword,application/x-shockwave-flash,*/*
ExceptionManager.UserLanguages: uk
ExceptionManager.MachineName: BigApple
ExceptionManager.TimeStamp: 04/07/2005 12:06:45
ExceptionManager.FullName: Microsoft.ApplicationBlocks.ExceptionManagement, Version=1.0.0.19886, Culture=neutral, PublicKeyToken=null ExceptionManager.AppDomainName: /GD/W3SVC/3/Root-9-127646767898906250
ExceptionManager.ThreadIdentity: felix
ExceptionManager.WindowsIdentity: BigApple\ASPNET
1) Exception Information *********************************************
Exception Type: System.Web.HttpException
ErrorCode: -2147024894
Message: Directory 'D:\WebProjects\XTend.CommonModules\Html\bin' does not exist.
TargetSite: System.Web.DirectoryMonitor FindDirectoryMonitor(System.String, Boolean, Boolean)
HelpLink: NULL
Source: System.Web
StackTrace Information *********************************************
at System.Web.FileChangesMonitor.FindDirectoryMonitor(String dir, Boolean addIfNotFound, Boolean throwOnError) at System.Web.FileChangesMonitor.StartMonitoringPath(String alias, FileChangeEventHandler callback) at System.Web.Caching.CacheDependency.Init(Boolean isPublic, Boolean isSensitive, String[] filenamesArg, String[] cachekeysArg, CacheDependency dependency, DateTime utcStart) at System.Web.Caching.CacheDependency..ctor(String[] filenames) at Microsoft.Toolkits.EnterpriseLocalization.Settings.get_CurrentManager() at Microsoft.Toolkits.EnterpriseLocalization.Localization.LocalizeControl(Control control) at Microsoft.Toolkits.EnterpriseLocalization.Localization.LocalizeControls(ControlCollection controls) at Microsoft.Toolkits.EnterpriseLocalization.Localization.LocalizeControls(ControlCollection controls) at XTend.ApplicationBlocks.Common.UI.GenericPage.Render(HtmlTextWriter writer)

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

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

Счастливо и приятной работы!


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

В избранное