О ненавистном GoTo
Исходный вариант. Этот материал был опубликован c незначительной литературной правкой в журнале BYTE/Россия N 11/2001.


О ненавистном GoTo (письмо читателя)

Прочитал я "ответ Чермберлену" Андрея Колесова ("Byte/Россия N 8/2001) про злополучный оператор GOTO. Я во многих утверждениях согласен с автором, кроме категоричного заявления о том, что современные языки программирования позволяют полностью исключить GOTO.

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

Просто GOTO - это жуть. Найти, куда по нему программа ушла - еще полгоря. А вот когда гадаешь, откуда она пришла на данную метку...

Юрий Максименко

В начало статьи

Ответ Андрей Колесова

К сожалению, из опубликованного в журнале варианта статьи исчез эпиграф, которые играл очень важную смысловую роль:

"Выражения типа "Пошел к...!" бывают порой уместны даже в самом изысканном обществе." Английская пословица

Это я к тому, что никто не говорит о том, что GOTO в принципе нельзя использовать. Порой это действительно уместно, но нужно четко понимать все возможные последствия. Да, и статья-то была о другом — о том, что серьезные вопросы программирования нужно вести на достаточно солидном уровне.

Что касается, конструкции On Error GoTo, то я вполне сознательно написал, что "современные языки программирования позволяют полностью исключить GOTO", так как просто GOTO и On Error GOTO — это две большие разницы.

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

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

Не очень понятна и жалоба на то, что "не знаешь откуда она пришла". На самом деле обработка ошибок в VB выглядит примерно следующим образом:


Sub MyProcedure
  ' включаем программную обработку ошибок
  ' с передачей управления на метку MyError
  On Error GoTo MyError
  ' далее идет программный код
  ...
  Exit Sub ' нельзя допустить прямого перехода в блок обработки

MyError:   ' обработка ошибки в процедуре
  ' анализ кода ошибки с помощью объекта Err
  ' блок обработки должен завершаться каким-то из этих операторов

  ' Resume  — передача управления на оператор, где произошла ошибка,
  '            т.е. повтор оператора
  ' Resume Next — передача управления на следующую строку
  '            после оператора, где произошла ошибка
  ' или просто завершить процедуру
End Sub

На что тут нужно обратить внимание? Действие оператора On Error автоматически отменяется при выходе из процедуры (Exit Sub или End Sub). Но он продолжает работать, если мы обращается к другой процедуре:


Sub MyProcedure
  On Error GoTo MyError
  Call MySub
  ...

При возникновении ошибки в подпрограмме MySub управление будет передано на метку MyError. Если у вас есть много других вложенных процедур, то понять где же произошла ошибка просто невозможно. Но проблема-то эта очень легко решается!

Один вариант — децентрализовать обработку ошибок и в каждой "критичной" процедуре организовывать свою обработку. При этом можно отменять программную обработку ошибок:


  On Error GoTo MyError  ' включили
  ...
  On Error GoTo 0        ' отключили
  Call MySub

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


 Public MyErrorPoint As String
 Sub MyProcedure
   On Error GoTo MyError
   ...
   MyErrorPoint = "MySub"
   Call MySub

Короче говоря, имеется много вариантов, как точно определить где произошла ошибка в программе.

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


  On Error Resume Next
  ...
  Open "TestFile" For Input As #1
  If Err.Number <> 0 Then  ' ошибка

В заключении хотел бы обратить внимание, что для анализа ошибок при выполнении функций Win32 API лучше пользоваться свойством LastDLLError объекта Err, а не обращаться к API-функции GetLastError. Дело в том, что в некоторых случаях VB обнуляет функцию GetLastError в промежутке между обращением к API-функции и продолжением выполнения программы.

В начало статьи