Ініціювання винятків
Вище вже говорилося про те, що метод ProcessFilе просто передає виняток у процедуру Sub Main, з якої він був викликаний. У процедурі Sub Mai n команда виклику теж поміщена в блок Try-Catch, тому виняток буде опрацьовано. З іншого боку, таке рішення виглядає трохи наївно, а якщо написані вами класи використовуватимуться іншими програмістами, воно стає просто небезпечним. Але навіть якщо справа обійдеться, користувачі вашого коду навряд чи будуть задоволені тим, що ви без розбору передаєте винятки, не намагаючись їх обробити.
Краще спробувати по можливості «прибрати» за собою, а потім скористатися ключовим словом Throw, щоб передати об'єкт виключення стороні, що викликає. У розділі 4 згадувалося у тому, що у VB .NET не підтримується детерміноване завершення. Отже, якщо ви створили об'єкт із методом D1 spose, цей метод слід викликати перед тим, як ініціювати виняток. Сказане стосується і відкриття файлів, і отримання графічного контексту. У наступному фрагменті представлена умовна структура такого коду:
' Створення локального об'єкта з методом Dispose
'Код. який може ініціювати винятки
Catch(e As Exception)
Втім, якщо ви дійсно хочете програмувати "як належить", не обмежуйтесь простим перезапуском виключення. Постарайтеся зробити свій код якомога інформативнішим і включіть до об'єкта виключення додаткову інформацію. Для цього є три можливості.
- Додайте у виняток змістове повідомлення та ініціюйте його заново. Можливо, нова інформація виявиться корисною.
- Ініціюйте виключення одного зі стандартних типів, похідних від типу поточного виключення, щоб воно краще описувалоситуацію.
- Створіть новий клас виключення, похідний від типу поточного виключення, який описуватиме ситуацію краще, ніж будь-який зі стандартних класів.
Рішення розташовані за зростанням пріоритету, і в ідеальному випадку завжди слід використовувати пункт 3. На практиці програмісти при виборі керуються своєю оцінкою того, яку інформацію про виключення необхідно передати для подальшої обробки.
Наприклад уявіть таку ситуацію: з джерела даних читаються пари «ключ/значення», й у останнього ключа немає парного значення. Програма передбачає, що значення асоціюється з кожним ключем, тому при спробі читання виникає несподіване виключення введення-виводу (читання даних із файлу описано в розділі 9).
Тепер ви хочете повідомити про те, що відбувається, стороні, що викликає. Щоб додати у виняток рядок, можна скористатися спеціальною версією конструктора класу Exception:
Public Sub New(ByVal message As String)
У наступному фрагменті до об'єкта IOException додається новий рядок із повідомленням про відсутність значення для останнього ключа, після чого виняток ініціюється заново.
Dim excep As New IQException("Missing value for last key") Throw excep
Отримавши ініційований виняток, зовнішній код отримує текст повідомлення методом Message класу Exception і дізнається про проблему.
Насправді у подібних ситуаціях частіше виникає виняток класу EndOfStream-Exception, похідного від IOException. Операції з потоками даних розглядаються у розділі 9.
Друга ситуація реалізується елементарно завдяки головному правилу успадкування: похідний клас може використовуватися замість базового класу. Вам лише залишаєтьсяініціювати виняток похідного класу, який найкраще підходить для цієї ситуації.
Останній випадок вимагає деякої додаткової роботи, оскільки цього потрібно визначити клас, похідний від існуючого класу винятку. Припустимо, ви хочете визначити новий клас виключення, похідний від System. 10. lOException. Новий клас відрізняється від старого лише одним ReadOnly-властивістю, що повертає ключ, з яким не асоціюється парне значення:
Public Class LastValueLostException Inherits System.I0.I0.Exception
Private mKey As String
Public Sub New(ByVal theKey As String)
MyBase.New("No value found for last key")
Public Readonly Property LastKey() As String Get
У цьому рядку викликається конструктор базового класу (і зрештою конструктор предка Exception).
Можливо, ви помітили, що в класі LastValueLostException не перевизначаються інші методи - такі як метод ToString, успадкований від Exception. У стандартних ситуаціях об'єкти винятків завжди мають виводити стандартні повідомлення.
Як використати створений клас у програмі? Наприклад, якщо останній ключ без парного значення дорівнював «oops», виняток ініціюватиметься наступною командою:
Throw New LastValueLostException("oops")
Врахуйте, що клас IOException, як і багато стандартних винятків, є похідним від Exception, а не від ApplicationException.
Виконавче середовище допомагає зробити наступний крок. Ієрархія винятків розходиться на дві гілки, показані на рис. 7.1.
Мал. 7.1.Дві основні гілки ієрархії винятків
Класи Exceptlon, AppllcationExcepti on і SystemExcepti on мають однаковуфункціональністю. Існування трьох класів замість одного – не більш ніж зручна абстракція, завдяки якій стає простіше зрозуміти винятки, що виникають у ваших програмах.
Винятки як заміна для goto
Обробка винятків разом із визначенням власних класів винятків дозволяє повністю відмовитися від використання GoTo. Наприклад, у розділі 3 наведено приклад виправданого застосування GoTo для переривання вкладених циклів, коли помилка відбувається у внутрішньому циклі. Програміст VB .NET у подібній ситуації просто укладає весь цикл у блок Try-Catch, як показано нижче:
Dim getData As String
Dim i, j As Integer
Dim e As System.I0.I0Exception
Для j = 1 To 100 Console.WriteC'Type the data, hit the Enter key between " & _
"ZZZ to end: ") getData _
Console.ReadLine() If getData = "ZZZ" Then
e New System.I0.I0Exception("Data entry ended " & _
"at user request") Throw e Else
У наведеному вище фрагменті виділені рядки не можна поєднати конструкцією наступного виду:
Dim e As New System.IO.IOException("Data entry ended at user request")
Внаслідок правил видимості VB .NET об'єкт виключення виявиться недоступним у розділі Catch.
При використанні блоків Try-Catch нерідко існує код, який має виконуватися як за нормального завершення, і у разі виключення. Наприклад, в обох випадках слід закрити файли, викликати методи Dispose і т. д. Навіть у простому прикладі, наведеному на початку розділу, була потрібна команда ReadLine, щоб консольне вікно залишалося на екрані до натискання клавіші Enter.
Щоб деякий фрагмент виконувався незалежно від того, виникнечи у програмі виняток чи ні, блок Try-Catch включається секція Finally, виділена в наступному прикладі жирним шрифтом:
Dim args (). argument As String
args = Environment. GetCommandLineArgs()
Console.WriteLine("Press enter to end")
Код секції Finally виконується до передачі винятків зовнішньому коду і до повернення з функції .
Рекомендації щодо використання винятків
Винятки виглядають ефектно, і новачки часто схильні зловживати ними. Справді, чи варто витрачати час на аналіз введення користувача, коли можна просто ініціювати виняток? Не піддавайтеся спокусі. При неправильному використанні обробка винятків суттєво уповільнює роботу програми. Нижче наведено деякі рекомендації щодо використання винятків у програмі.
- Виняток є ознакою аварійної ситуації; не використовуйте виняток для простої передачі інформації (ми бачили програму, в якій при успішному завершенні функції ініціювалося виключення SUCCESS_EXCEPTION).
- Не замінюйте тривіальні перевірки обробкою винятків. Наприклад, винятки не варто застосовувати для перевірки досягнення кінця файлу (EOF).
- Уникайте роздробленої обробки винятків, коли чи не кожна команда полягає в окремий блок Try-Catch. Висновок всієї операції в один блок Try-Catch зазвичай краще використання декількох блоків.
- Не поглинайте виняток конструкціями виду Catch e As Excepti on з порожнім блоком команд, якщо для цього немає достатньо вагомих причин. Така конструкція еквівалентна бездумному застосуванню On Error Resume у старих програмах VB, і користуватися нею небажано з тих самих причин. Якщо у програмі сталосявиняток, обробіть його або передайте для подальшої обробки.
- Останню рекомендацію можна назвати «правилом хорошого тону». Передаючи виняток у зовнішній код для подальшої обробки, додайте в нього нову інформацію (або визначте новий клас виключень), щоб зовнішній код міг точно визначити, що сталося і які заходи було вжито для того, щоб виправити ситуацію.