Дата публикации статьи: 02.01.2005 23:22

Обработка ошибок в VB.NET

Автор: Evangelos Petroutsos
Mastering™ Visual Basic® .NET
San Francisco London
Copyright ©2002 SYBEX, Inc., Alameda, CA www.sybex.com
Перевод: Игорь Шувалов aka Ronin

 

Написание программного обеспечения, даже самого маленького, может быть очень трудной задачей. Разработчики обычно тщательно обдумывают и планируют задачи, и методики, используемые для решения этих задач. Комплексная разработка ПО постоянно ведет к ошибкам в программировании. Эта статья создана для того, чтобы познакомить вас с описанием различных типов ошибок, которые вы можете случайно встретить в процессе программирования на Visual Basic .NET, некоторыми инструментами, которые вы можете использовать для обнаружения ошибок, и о специальных структурах программирования, применяемых для предупреждения ошибок в работающей программе.
Вдобавок к ошибкам, ваше приложение должно быть готово гибко обработать все неправильные условия, от тех, которые могут произойти по вине пользователей (когда, например, вводят строку вместо числового значения или даты) до неисправных устройств, или простейших ситуаций, связанных с невозможностью записать данные в файл из-за использования этого файла другим пользователем. Все эти условия могут не коснуться вашей программы, но для этого ваше приложение должно специальным образом их обработать. Ваше приложение может показывать предупреждения и сообщения об ошибках, но ни в коем случае не должно завершиться крахом.

Типы ошибок

Ошибки, вызываемые компьютерной программой (не важно на каком языке она написана), могут быть разделены на три основных типа: Design-time (во время проектирования), Runtime (во время выполнения) и Logic (логические).
Design-time ошибки легче всего обнаружить и исправить. Такого рода ошибки возникают при написании кусков кода, которые не удовлетворяют правилам того языка, на котором они пишутся. Их легко найти, потому что Visual Studio .NET не только указывает на область их возникновения, но и на конкретное выражение.
Runtime ошибки обнаружить сложнее, потому что VS не дает никакой помощи в их обнаружении до непосредственного возникновения их в программе. Они возникают при попытке выполнить недопустимое действие, например, получить доступ к данным, которые не существуют, или к ресурсам, к которым приложение не имеет прав доступа. При возникновении такого рода ошибок, если они не будут обработаны должным образом, приложение может либо закончиться крахом, либо зависнуть.
Третий тип ошибки, логические, являются самыми коварными и сложными в обнаружении, т.к. они не оказывают никакого влияния на работоспособность приложения. Наличие в программе логических ошибок означает лишь то, что вы будете получать неверные результаты от работы программы. Это могут быть простейшие ошибки при неверной калькуляции, или пункт меню, который вы забыли установить в положение disabled и т.д.

Исключения

Runtime ошибки в Visual Basic .NET вызывают исключения. Исключение – это реакция на ошибку, сгенерированную приложением. При возникновении исключения появляется диалог, сообщающий об ошибке с дополнительной информацией. Этот диалог дает возможность пользователю продолжить выполнение программы. В редких случаях это может быть полезным, но чаще всего это не желательно. Продолжение выполнения программы может повлечь за собой неисправимые ошибки, например записи в базу данных неверных (ошибочных) данных.
Если мы не хотим, чтобы пользователь мог обрабатывали исключения, то мы можем сами легко обрабатывать их программно. Модель обработки ошибок в Visual Basic .NET позволяет сделать это очень легко. Обработчик ошибок в VB.NET – это блок кода, который может обнаружить исключения и выполнить необходимые действия для исправления этой ошибки. Вот примеры ошибок.

Private Sub Button2_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button2.Click
  Dim s As String
  s = "answer"
  Button2.Text = s.Substring(10, 1)
End Sub

Этот код пытается отобразить одиннадцатый символ строки s. Как вы видете, строка s содержит только 6 символов, что и приведет к ошибке. Давайте посмотрим точное определение исключения:

An unhandled exception of type ‘System.ArgumentOutOfRangeException’ occurred in
mscorlib.dll
Additional information: Index and length must refer to a location within the string.

Первое, что помогает нам убедиться в том, что это runtime ошибка – это слово unhandled. Это значит, что ошибка была в строке, не входящей в блок обработчика исключений. Еще один интересный момент, это тип вызванного исключения System.ArgumentOutOfRangeException. Очень важно знать то, что разные типы ошибок разделяются в группы. Это важно, когда вы реализуете механизм обработки ошибок. Исключения создают объект, наследуемый от класса Exception.
Блок “Additional information” («дополнительная информация»), дает нам некоторую специфическую информацию о природе возникновения ошибки.

Обработка исключений

Давайте приведем пример обработки ошибок на примере предыдущего кода:

Private Sub Button2_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button2.Click
  Dim s As String
  s = "answer"
  Try
    Button2.Text = s.Substring(10, 1)
  Catch
    Button2.Text = "error"
  End Try
End Sub

Это тот-же код, но теперь выражение, вызывающее ошбку помещено в блок обработки исключений Try…Catch…End Try. Этот блок представляет собой простейший обработчик исключения. Если в блоке Try генерируется исключение, то программа автоматически переходит в блок Catch. Если никаких исключений не генерируется, то программа пропускает блок Catch. При выполнении этого кода, происходит исключение System.ArgumentOutOfRangeException, но программа продолжает выполняться и не показывает никаких сообщений. После выполнения свойство Text кнопки Button2 будет иметь значение "error".

В следующем примере, исключение генерирует обьект ex класса Exception.

Private Sub Button2_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button2.Click
  Dim s As String
  s = "answer"
  Try
    Button2.Text = s.Substring(10, 1)
  Catch ex As Exception
    MsgBox(ex.Message)
  End Try
End Sub

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

Private Sub Button3_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button3.Click
  Try
    Button3.Text = lbStates.SelectedItem.ToString
  Catch ex As System.NullReferenceException
    MsgBox("Please select an item first")
  Catch ex As Exception
    MsgBox("Some other error: " & ex.Message)
  End Try
End Sub

Этот код пытается получить выбранный элемент из ListBox’a lbStates. Если в lbStates не выбрано ни одного элемента, то генерируется исключение System.NullReferenceException. Это исключение обрабатывается специальным обработчиком, который вызовет MsgBox с соответствующим сообщением ("Please select an item first"). Если же произойдет исключение какого-либо другого типа, то оно будет обработано другим обработчиком.
Заметьте, что в предыдущем коде обработчик специфичного типа исключений стоит ранее обработчика общего типа. Это определяет порядок обработки исключений.
Также заметьте, что объект ex присутствует в каждом обработчике. Это становится возможным, потому, что это объявление несет локальный характер, и область видимости этого объекта ограничивается соответствующим блоком Catch.

Блок Finally

При использовании структуры обработки исключений Try…Catch…End Try исключение обрабатывается первым релевантным обработчиком. После этого программа уходит за рамки этой структуры. Иногда требуется выполнить какой-либо код, перед тема как выйти из структуры Try…Catch…End Try. Как, например, в следующем коде.

Protected Sub ReadFromATextFile(ByVal cFilename As String)
  Dim s As StreamReader
  Dim cLine As String
  Dim bDone As Boolean = False
  lbresults.Items.Clear()
  s = New Streamreader(cFilename)
  Try
    While Not bDone
      cLine = s.ReadLine()
      If cLine Is Nothing Then
        bDone = True
      Else
        lbresults.Items.Add(cLine)
      End If
    End While
    s.Close()
  Catch ex As Exception
    MsgBox("some error occurred")
  End Try
End Sub

В этом коде читается файл и построчно загружается в ListBox. Если возникнет исключение в цикле, то мы не сможем закрыть Streamreader (вызвать s.Close()), что может повлеч к ненужной трате ресурсов.
К счастью, для этого существует специальный блок, код которого будет выполнен в любом случае. Этот блок называется Finally. Вот измененный код из предыдущего примера.

Protected Sub ReadFromATextFile(ByVal cFilename As String)
  Dim s As StreamReader
  Dim cLine As String
  Dim bDone As Boolean = False
  lbresults.Items.Clear()
  s = New Streamreader(cFilename)
  Try
    While Not bDone
      cLine = s.ReadLine()
      If cLine Is Nothing Then
        bDone = True
      Else
        lbresults.Items.Add(cLine)
      End If
    End While
    s.Close()
  Catch ex As Exception
    MsgBox("some error occurred")
    Finally
    s.Close()
  End Try
End Sub

Теперь в любом случае Streamreader будет закрыт и ресурсы будут высвобождены.


Вызов исключений

При создании собственных классов, вы, вероятно, встретитесь с необходимостью вызова исключений. Это сделать очень просто – для этого существует ключевое слово Throw. Разработчик, использующий ваш класс, сможет обработать это исключение. Вот пример вызова (срабатывания) исключения.

Private FValue As Integer = 0

Property Value() As Integer
Get
  Return FValue
End Get
Set(ByVal iValue As Integer)
  If iValue <= FMax Then
    FValue = iValue
  Else
    FValue = FMax
    Throw New OverflowException(_
    “Cannot set ProgressBar value to greater than maximum.”)
  End If
  Invalidate()
End Set

Здесь вызывается исключение OverflowException с вашими комментариями.