Пособие-самоучитель on-line "Visual Basic с нуля"
Глава 11. Перехват и обработка ошибок. Операторы On Error и Resume. Объект Err. Оператор GoTo.
Скачать исходник программы Error
Дата создания 11.01.2005 {Автор 4us}

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

Хотя и не все ошибки VB может перехватывать (их полный список можно найти здесь), однако избегать стандартных ошибок, обрабатывать явно возможные ошибки мы просто обязаны. Мне представляется, что для исключения фатальных сбоев надо придерживаться следующей стратегии.

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

Во-вторых, надо стараться избегать возникновения ошибки программно, тогда ее не придется отслеживать и обрабатывать. Ярким примером такой ситуации может служить программа Scandisk, которую мы рассматривали в прошлой, 10-ой главе. Когда мы тыкали мышью в Dir1 по директориям, все было нормально, при этом выполнялся следующий оператор:
FileName = Dir(OurDir & "\*.*", 0) 'присваиваем переменной значение функции Dir для всех файлов
Если же мы кликали по верхней строке с именем не директории, а диска, возникала ошибка 52 "Неправильное имя файла, или номер". А дело то всего лишь в слеже, который мы поставили в операторе. Дело в том, что функция Dir возвращает имя диска со слежом в конце строки, а имя директории - нет. Если мы его уберем и оператор будет выглядеть так
FileName = Dir(OurDir & "*.*", 0) 'присваиваем переменной значение функции Dir для всех файлов
вот тогда на диске оператор сработает правильно, зато перестанет читать директории. Можно конечно обработать эту ошибку, о чем мы будем говорить ниже, по ее номеру, но тогда в трех нижних операторах
Attr = GetAttr(OurDir & "/" & FileName)...
той же причиной будет также вызвана ошибка, но с другим номером - 75 "Ошибка доступа пути или файла". Тогда придется обрабатывать и ее. Это тоже вариант. Но по-моему логичнее сразу записать в переменную Ourdir правильный путь при любом раскладе. И надо-то для этого всего лишь убрать слеж из четырех операторов, где возникает ошибка, а в процедуру Dir1_Change добавить проверку, которая добавляет слеж при его отсутствии:
If Right(OurDir, 1) <> "\" Then OurDir = OurDir & "\" 'добавленный оператор для устранения ошибки
вот и все дела. Полный исправленный код, если что не понятно, можно скачать здесь.

В-третьих, при обращении к внешним источникам, в том числе к файлам, для получения или вывода информации надо предполагать, что возможно они будут недоступны. Кроме того, надо подумать, какие еще фатальные ситуации могут возникнуть. Вот тут уже надо обрабатывать ошибки конкретно.
Давайте создадим новый exe-проект. По мере необходимости на форму будем класть командные кнопки и в их процедуре отлавливать ошибки. Возьмем пример самой распространенной ошибки - нет того файла, который нам как раз крайне необходим. Положим на форму кнопку Command1 (она будет называться Resume Next) и в ее процедуре напишем операторы открытия файла "aaa.txt" для последовательного чтения.

Option Explicit

Private Sub Command1_Click()
Open "aaa.txt" For Input As #1
Close #1
End Sub

Поскольку такого файла никто не создавал, то при запуске программы и нажатии кнопки Command1 возникнет ошибка 53, о чем нам любезно сообщит Windows: "Run-time error '53': Файл не найден". Программа аварийно завершится.
Итак, что же нам может предложить VB для предотвращения фатальных ошибок.

Операторы ON ERROR и RESUME (Resume Next).

Перво-наперво, ошибку надо отловить. Сделать это можно с помощью оператора On Error. Однако сам по себе этот оператор работать не будет, это и логично, с отловленной ошибкой ведь надо что-то делать. Самое простой и безбашенный метод (кстати довольно эффективный, когда постоянно возникает ошибка, справиться с ней не можешь и это тебя достало) - присобачить к оператору On Error оператор Resume Next. Тогда получившийся оператор On Error Resume Next в случае возникновения ошибки возвратится в программу и передаст управление оператору, следующему за тем оператором, где возникла ошибка. Однако следует помнить, что On Error Resume Next действует во всей процедуре, и если процедура большая и сложная, то ошибка может возникнуть совсем в другом операторе, нежели ты ожидаешь. Она все равно будет обработана, VB перескочит на следующий оператор, а ты об этом ничего не узнаешь и будешь дня два ломать голову, почему не работает то-то и то-то, хотя все прописано в коде черным по-белому. Ну, тем не менее исправим наш код с использованием оператора On Error:

Private Sub Command1_Click()
On Error Resume Next
Open "aaa.txt" For Input As #1
Close #1
Form1.Print "Процедура кое-как завершена"
Form1.Print "с помощью On Error Resume Next"
Form1.Print "файл не считан, работать не с чем"
End Sub

Текст в операторах Form1.Print написан условно, так как ясно, что он будет выводится независимо от того, есть ошибка или нет. Т.е на самом деле пользователь никакой реальной информации об ошибке не получит и поправить ничего не сможет. Иное дело, если мы к нашему On Error попробуем пристроить оператор безусловного перехода GoTo метка. На самом деле он унаследован от старого Бейсика и использовать его нужно крайне осторожно. Этот оператор отсылает ( в нашем случае, если возникла ошибка) к строке, помеченной меткой, но только внутри текущей процедуры. Положим следующую кнопку Command2 (ее назание GoTo метка) и в ней напишем процедуру

Private Sub Command2_Click()
Dim Msg As String
On Error GoTo ErrorMark
Open "aaa.txt" For Input As #1
Close #1
Exit Sub
ErrorMark:
Msg = "Ой, наверно файл куда-то запропал, попробуйте восстановить!?"
MsgBox Msg, , "Караул, нет данных!"
End Sub


Таким образом, при возникновении ошибки оператор GoTo возвращает нас в программу к строке, помеченной меткой ErrorMark. Под ней пишутся операторы, которые выполняются для реакции программы (или юзера) на ошибку. Обратите внимание, чтобы они не выполнялись в случае, когда ошибки нет, перед меткой поставлен оператор Exit Sub, который прерывает выполнение процедуры, не дожидаясь оператора End Sub. В качестве реакции на ошибку у нас выводится MessageBox с советом для юзера. Обратите снова Ваше драгоценное внимание на то, что в сообщении я употребил слово "наверное". Реально при открытии файла могут возникать разные ошибки и я лишь только предполагаю, что у нас ошибка 53.
Как я говорил выше, в сложных и больших процедурах, чтобы не отлавливать ошибки в других операторах оператор GoTo метка. надо выключить. Для этого, после сомнительного оператора, могущего вызвать ошибку, надо вставить строку
On Error GoTo 0
Тогда обработчик ошибок прекратит свою работу и ошибки в последующих операторах отслеживаться не будут.
Если в подпрограмме обработки ошибок имеется возможность устранить причину ее возникновения, то надо вернуться на тот же оператор и снова его выполнить, уже без ошибки. Для этого подпрограмму обработки ошибок заканчивают оператором Resume. Если же надо перейти к другой строке, помеченной меткой, то программу обработки ошибок надо закончить оператором Resume метка. Ну а теперь перейдем к самому интересному - анализу ошибок.

Объект Err.
Вот как раз с помощью этого объекта мы можем проанализировать, какая ошибка произошла, т.е. по сути, получить ее номер, и в зависимости от ситуации программно решить вопрос об ее устранении или наоборот, заставить юзера производить какие-либо судоржные действия.
Основным свойством объекта Err является свойство .Number, которое и возвращает нам номер ошибки. Настолько основным, что .Number можно не писать. Синтаксис будет такой:
Переменная=Err.Number или Переменная=Err
Это свойство для нас главное. Кроме того (для сведения) имеются еще такие свойства:
.Sourse - имя текущего проекта VB
. Description - cтрока, соответствующая строке, возвращаемой функцией Error для кода ошибки, указанного в свойстве Number, если такая строка существует
.HelpFile - полное имя (включая диск и путь) файла справки Visual Basic.
. HelpContext - контекстный идентификатор файла справки Visual Basic, соответствующий ошибке с кодом, указанным в свойстве Number.
. LastDLLError - содержит системный код ошибки для последнего вызова DLL (только для 32-разрядных Microsoft Windows)

Положим на форму Text1 со свойством Multiline - True, кнопку Command3 (название - Прочитать файл), а также для того, чтоб нам было удобно создавать, убивать файл, положим еще три кнопки Command4, Command5 и Command6, называющиеся соответственно "Создать aaa.txt", "Создать пустой aaa.txt" и "Убить aaa.txt". В процедурах этих трех кнопок напишем:
код для создания файла путем открытия и записи туда слов "не пустой" в Command4

Private Sub Command4_Click()
'создать aaa.txt
On Error Resume Next
Kill (App.Path & "\aaa.txt")
Open App.Path & "\aaa.txt" For Output As #1
Print #1, "не пустой"
Close #1
Text1.Text = "Создан не пустой файл aaa.txt"
End Sub


в Command5 создадим пустой файл, т.е. файл не содержащий ни одного символа

Private Sub Command5_Click()
'создать пустой aaa.txt
On Error Resume Next
Kill (App.Path & "\aaa.txt")
Open App.Path & "\aaa.txt" For Output As #1
Close #1
Text1.Text = "Создан пустой файл aaa.txt"
End Sub


и в Command6 - программу для уничтожения файла

Private Sub Command6_Click()
'убить aaa.txt
On Error Resume Next
Kill (App.Path & "\aaa.txt")
Text1.Text = "Убит файл aaa.txt"
End Sub


Обратите внимание, что перед созданием файла, я убиваю старый. Так как старый файл может на тот момент не существовать (в этом случае возникнет ошибка), я использую On Error Resume Next.
Теперь создадим коротенькую процедуру загрузки формы для проверки в самом начале, существует ли файл aaa.txt

Private Sub Form_Load()
Text1.Text = ""
If Dir(App.Path & "\aaa.txt") = "" Then Text1.Text = "Файла aaa.txt не существует"
End Sub


А вот теперь в кнопке Command3 напишем полноразмерный обработчик ошибок.

Private Sub Command3_Click()
'объявляем переменную для считывания данных из файл
а
Dim Stroka As String
'использование объекта Err при обработке ошибок
On Error GoTo ErrorMark
ForOpen:
Open App.Path & "\aaa.txt" For Input As #1
Line Input #1, Stroka
Text1.Text = Text1.Text & "Файл успешно открыт, содержание файла: " & Stroka & vbCrLf
Close #1
Exit Sub
ErrorMark:
Close #1
'выводим возможные данные об ошибке
Text1.Text = "Возникла ошибка # " & Err.Number & vbCrLf
Text1.Text = Text1.Text & Err.Description & vbCrLf
Text1.Text = Text1.Text & "В " & Err.Source & vbCrLf
Text1.Text = Text1.Text & "Полный путь хелпа " & Err.HelpFile & vbCrLf
Text1.Text = Text1.Text & "Идентификатор файла спраки VB " & Err.HelpContext & vbCrLf
Text1.Text = Text1.Text & "DLL " & Err.LastDllError & vbCrLf
'Ecли файла нет, то создем его
If Err = 53 Then
Open App.Path & "\aaa.txt" For Output As #1
Print #1, " "
Close #1
Text1.Text = Text1.Text & "Создан новый файл aaa.txt" & vbCrLf
'возвращаемся на исходную позицию
Resume ForOpen
'если файл пустой дописываем в него пробел
ElseIf Err = 62 Then
Open App.Path & "\aaa.txt" For Append As #1
Print #1, " "
Close #1
Text1.Text = Text1.Text & "Ошибка файла aaa.txt исправлена " & vbCrLf
'возвращаемся на исходную позицию
Resume ForOpen
Else
Text1.Text = Text1.Text & "Не знаю, что делать с ошибкой, файл не открыт "
End If
End Sub

Теперь, если мы убъем файл кнопкой "Убить aaa.txt" и нажмем кнопку "Прочитать файл", то в Text1 выведется вся доступная информация об ошибке, создастся новый файл и процедура начнет выполнятся по-новой уже с существующим файлом. Аналогичная ситуация возникает при создании пустого файла. При чтении такого файла возникает ошибка 62 - чтение после конца файла. Мы также получаем информацию об ошибке, дописываем в файл пробел и по Resume вновь запускаем процедуру. Если возникает ошибка с номером, который мы не знаем, то просто выводим об этом сообщения в Text1.

Замечу, что с операторами Open и Close надо обращаться аккуратно. Все файлы должны своевременно открываться и закрываться. Если путаешься с работой с файлами, посмотри Главу 7. Скачать исходник можешь вверху страницы.

Резюме:
Таким образом, обработка ошибки включает в себя:
- Обнаружение ошибки On Error.
- Переход на подпрограмму обработки GoTo (или, на крайняк, на следующий оператор с помощью Resume Next).
- Определение ошибки Err.Number и в зависимости от этого выполнение каких-то действий.
- Возврат Resume после устранения ошибки на оператор, вызвавший ошибку.

Для очистки совести скажу еще про функцию Error. Она по номеру ошибки выводит пояснение - что это за ошибка. Синтаксис:
Строковая переменная=Error(номер ошибки)
Если хотите, положите на форму последнюю кнопку Command7 и для вывода пояснений к ошибкам, например с 52 по 62 напишите такой код и посмотрите, что получится.

Private Sub Command7_Click()
Dim ErrorNumber As Long
Text1.Text = ""
For ErrorNumber = 52 To 64
Text1.Text = Text1.Text & Error(ErrorNumber) & vbCrLf
Next ErrorNumber
End Sub


Совершенно ничего сложного. Ну-с с этим все.

Copyright © 2005 4us



Сайт создан в системе uCoz