Как эффективно обрабатывать ошибки в приложениях Access

Источник: sgml
Андрей Семенов

 

Как всем вам хорошо известно, программ без ошибок не бывает. Даже такие монстры, как Microsoft регулярно публикуют списки официальных багов, патчи и.т.д. В приложениях на основе Access, разрабатываемых одним-двумя программистами вероятность появления ошибок (Run-Time errors) на стадии эксплуатации равна 100%. Наша задача таким образом, состоит не только в том, чтобы исключить ошибки на стадии программирования и тестирования, но и эффективно обнаружить и устранить неполадки в процессе работы. Здесь возникает 2 проблемы:

1)                    Техническая. Как правило, ошибки  на стадии эксплуатации возникают при совпадении нескольких факторов, все комбинации которых заранее протестировать невозможно. Для устранения таких ошибок необходимо в первую очередь их воспроизвести и понять причину. Однако как правило это затруднительно: разработчик может быть удалён в пространстве и времени, пользователь может не запомнить при каких условиях произошла ошибка.

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

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

Компоненты решения

Все сказанное здесь и ниже в основном относится к клиент/серверной архитектуре, где Access выполняет роль клиента, однако может быть распространено и на Access базу, используемую в многопользовательском режиме.

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

Итак, для получения полной картины происшествия нам поможет следующая информация:

1)      Имя машины, на которой возникла проблема и имя пользователя домена

Для того, чтобы определить имя рабочей станции, на которой выполняется Access, достаточно прочитать значение переменной окружения ENVIRON ("COMPUTERNAME"), и имя пользвователя - ENVIRON ("USERNAME"), однако в ряде случаев (например если у вас Win Me рабочие станции с UNIX PDC)  это может не сработать. Есть более надёжный способ, основанный на API вызовах - в приложенном файле вы найдёте Class Module SystemInfo в котором реализованы функции. Так или иначе, нам понадобятся функции GetUserName() и GetCompName()

Public Function GetUserName()

    Dim si As New SystemInfo

    GetUserName = si.UserName

End Function

Public Function GetCompName()

    Dim si As New SystemInfo

    GetCompName = si.ComputerName

End Function

2)      Путь к базе и имя пользователя MS Access

Легко определяются встроенными функциями:

CurrentDB.Name

Application.CurrentUser

3)      Имя библиотеки, вызвавшей ошибку, номер и описание ошибки

Встроенный объект Err при возникновении ошибки заполняется аттрибутами ошибки. Считать их просто:

Err.Source

Err.Number

Err.Description

4)      Дата и время возникновения

Вставляем текущую дату и время: Now()

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

5)      Имя активной формы / отчёта, Имя активного элемента управления, предыдущего элемента управления и его родителя

Для этой цели воспользуемся малоизвестным объектом Screen:

Function collectActiveControls(ByRef AC As String, ByRef AR As String, ByRef AF As String, ByRef pc As String, ByRef PCP As String)

On Error Resume Next

AC = Nz(Screen.ActiveControl.Name, "")

AR = Nz(Screen.ActiveReport.Name, "")

AF = Nz(Screen.ActiveForm.Name, "")

pc = Nz(Screen.PreviousControl.Name, "")

PCP = Nz(Screen.PreviousControl.Parent.Name, "")

End Function

Я вынес определение этих величин в отдельную функцию, так как оно само по себе может вызывать ошибки (например, если активный отчёт отсутствует), однако если определять их в функции ReportError, то значения Err.Description, Err.Number и Err.Source в случае ошибки окажутся сброшенными и замененными на ошибку определения активных элементов управления. Поэтому я сперва считываю параметры объекта Err в переменные, а затем запускаю collectActiveControls

6)      Имя процедуры VBA, в которой произошла ошибка

Это наиболее хлопотная часть, так как в VBA нет средства, позволяющего коду узнать, какая процедура выполняется в данный момент. Единственный способ это сделать, - заранее записать в обработчик ошибки имя процедуры. Ни вам ни мне этого делать, естественно, не хочется. По счастью, эту работу можно автоматизировать. С этим справляется Error Handler, поставляемый с Microsoft Office Developer Edition, однако ODE птица редкая, поэтому я предпочитаю Error Handler Builder от Zada Solutions, благо бесплатное, скачать его можно здесь: http://www.zada.com.au/accessaddins.htm

Что же умеет этот софт? Вы можете создать шаблон обработчика ошибок и затем переделать по его образцу выделенную процедуру (или все процедуры).

Шаблон выглядит примерно так:

'Error-handler inserted on [DATE] at [TIME] by [AUTH]

'[NOTE]

On Error GoTo Whoops

[BODY]

Goon:

Exit [PRTY]

Whoops:

ReportError "[FUPN]"

Resume Next

Все, что в квадратных скобках заменяется на конкретные значения. Как это работает? Встречая ошибку мы переходим к метке Whoops, вызываем функцию ReportError, c именем модуля и функции в качестве аргумента. Функция собирает всю информацию, пишет её в прилинкованную серверную табличку, далее в зависимости от режима сообщает об ошибке пользователю и продолжает работу. Здесь возможны два варианта:

1)      Работа продолжается со следующей строки (Resume Next) - я предпочитаю именно этот вариант, так как мой опыт подсказывает, что большинство ошибок не фатальны, и можно продолжать работать

2)      Вариант, когда в случае ошибки необходимо выйти из функции, не совершая более никаких действий - просто замените Resume Next на Resume Goon

Пример функции, обработанной Error Handler:

Function ErrorGenerator()

'Error-handler inserted on 20.07.2003 at 16:06 by Andrew

'

On Error GoTo Whoops

   

    MsgBox 1 / 0

   

Goon:

Exit Function

Whoops:

ReportError "mdService_ErrorGenerator"

Resume Next

End Function

Наконец функция ReportError:

Function ReportError(Optional PName As String)

"Pname - имя процедуры

Dim Q As String

Dim AC$, AR$, AF$, pc$, PCP$, ES$, ED$, EN$

Q = Chr(34)

"код ошибки, библиотека и описание

EN = Err.Number

ES = Err.Source

ED = Err.Description

"собираем информацию о элементах управления

Call collectActiveControls(AC, AR, AF, pc, PCP)

"отключаем предупреждение о вставке данных в таблицу

DoCmd.SetWarnings False

"вставляем в таблицу собранные данные:

        CurrentDb.Execute "INSERT INTO Bugs (Machine, User, AccessUser, MDate, ErrNumber, ErrDescription, ErrSource, ActiveForm, ActiveControl, ActiveReport, PreviousControl, PCParent, [Procedure]) VALUES " _

    & "('" & GetCompName() & "','" & GetUserName() & "','" & Application.CurrentUser & "','" & Now() & "'," & EN & "," & Q & ED & Q & ",'" & ES & "','" & AF & "','" & AC & "','" & AR & "','" & pc & "','" & PCP & "','" & PName & "');"

DoCmd.SetWarnings True

"здесь решаем - ставить ли в известность пользователя

    Select Case DebugMode

    Case 0

"режим отладки - для себя

        Stop

    Case 1

"MsgBox пользователю. Не рекомендуется

        MsgBox "Возникла непредвиденная ошибка. Если подобная ошибка будет возникать впредь, пожалуйста обратитесь к разработчику", vbOKOnly & vbExclamation, "Не хотелось бы вас беспокоить, но..."

    Case 2

"Выводим текст описания ошибки в строку состояния, ждём 1 секунду (мне лично достаточно) и очищаем строку.

        SysCmd acSysCmdSetStatus, ED

        pause (1)

        SysCmd acSysCmdClearStatus

    Case 3

"не делаем вообще ничего

    End Select

End Function

В принципе всё должно быть понятно - собираем информацию и записываем в таблицу. Интересное здесь в конце: в зависимости от DebugMode

Function DebugMode()

'

    DebugMode = 2

'0 - Stop

'1 - MsgBox

'2 - текст в строку состояния

'3 - молча

End Function

-подавляется сообщение пользователю, либо мигает на секунду в строке состояния, так, чтобы успели заметить вы (если не успеваете, замените pause(1) на pause(x) - количество секунд. Функция pause (параметр hg - отображать ли песочные часы):

Function pause(seconds As Long, Optional hg As Boolean = True)

Dim t As Double

If hg Then

    DoCmd.Hourglass True

End If

t = Now

While DateDiff("s", t, Now()) < seconds

'DoEvents

Wend

If hg Then

    DoCmd.Hourglass False

End If

End Function

Как этим наслаждаться?

Здесь можно скачать файл с макетом таблицы и всеми процедурами - их нужно вставить в вашу базу. Если у вас клиент/сервер, таблицу bugs лучше экспортировать на сервер и прилинковать. Если нет - советую сделать отдельную "админскую" базу, закинуть таблицу туда и прилинковать ко всем клиентским.

Далее следует скачать и установить Error Handler Builder -http://www.zada.com.au/accessaddins.htm Написать и сохранить макет обработчика ошибок.  Прогнать его по всем процедурам и функциям, предварительно удалив те обработчики, что там есть. Обращаю внимание на то, что на сами функции обработки ошибок обработчики вставлять не надо.

Готово.

Все ошибки сыпятся в табличку. Вам остаётся сидеть и наблюдать. Можно написать форму с таймером, повесить алерты админу , итд.  Обратите внимание, что если вы выбрали DebugMode 2 или 3 то пользователь ошибок не заметит, поэтому следите за ними сами. В табличке Bugs предусмотрено поле Solution, куда рекомендую записывать выясненную причину и метод решения, - потом при возникновении аналогичной ошибки (или последовательности) будет легче найти решение.

Литература по теме:

1)      Литвин, Гетц, Гилберт. Access 2000 Руководство разработчика т. 2. Киев, BHV 2001

2)      В.И. Король. Visual Basic.Net, Visual Basic 6.0, Visual Basic for Applications 6.0, М.: Кудиц-Образ 2002

3)      М.Тэллес, Ю.Хсих. Наука отладки. М.: Кудиц-Образ 2003

 


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=11697