Установка состояния ошибки с THROW
Директива THROW использовалась во многих уже рассмотренных примерах. Рассмотрим некоторые дополнительные способы использования THROW:
- THROW с объектами ошибок
- THROW по умолчанию на уровне процедур
- THROW и пользовательские функции
THROW с объектами ошибок
Опция UNDO, THROW как часть фразы ON ERROR приводит к откату текущей транзакции, подавлению выдачи системного сообщения об ошибке, созданию и «заполнению» объекта ошибки, и передаче этого объекта для обработки. Если ошибка не обрабатывается (CATCH) в текущем блоке, она передается для обработки блоку, охватывающему его (распространение ошибки вверх по стеку вызовов).
И в традиционной, и в структурной обработке ошибок обработка по умолчанию выполняется максимально «близко» к месту, где возникла ошибка. Распространение ошибки вверх по стеку вызовов поддерживает другую технику. Предположим, Ваше приложение обрабатывает все предсказуемые ошибки. При этом неожиданные системные ошибки все равно остаются проблемой. Теперь можно использовать THROW для передачи необработанных ошибок от блока, где они произошли, во внешние блоки, до достижения некоторого централизованного обработчика.
Директива THROW в операторе UNDO позволяет установить ошибку приложения в блоке, в котором расположен этот оператор:
IF CurrentTime > ClosingTime THEN UNDO,
THROW NEW Progress.Lang.AppError
(“Can’t take a delivery order after closing time.”, 550).
Здесь создается объект Progress.Lang.AppError с параметрами, которые добавляют в новый объект текст и номер сообщения.
Это же самое может быть выполнено с помощью оператора RETURN ERROR:
IF CurrentTime > ClosingTime THEN RETURN
ERROR NEW Progress.Lang.AppError
(“Can’t take a delivery order after closing time.”).
В отличие от UNDO, THROW, оператор RETURN ERROR не выполняет действие отката.
Возврат объекта ошибки RETURN ERROR error–object–expression поддерживают следующие элементы языка ABL:
- Оператор RETURN
- Фраза ON ENDKEY
- Фраза ON ERROR
- Фраза ON QUIT
- Фраза ON STOP
- Оператор UNDO
THROW по умолчанию на уровне процедур
Откатываемые блоки (DO, FOR и REPEAT) поддерживают явную фразу ON ERROR UNDO, THROW. Такая фраза используется для распространения ошибок вверх по стеку вызовов, так что они могут быть обработаны в блоке CATCH, связанном с блоком более высокого уровня. Это позволяет избежать необходимости размещать блоки CATCH для обработки общих ошибок на каждом уровне серии вложенных блоков.
Главные (main) блоки процедур ABL (routine-level blocks) не поддерживают явных фраз ON ERROR. Неявно для процедур действует ON ERROR UNDO, RETRY или ON ERROR UNDO, LEAVE, в зависимости от наличия или отсутствия операторов ввода данных.
В процедуре (файле .p), или классе (.cls) Вы можете изменить неявную фразу ON ERROR с помощью следующего оператора:
ROUTINE-LEVEL ON ERROR UNDO, THROW.
Этот оператор заменяет неявную фразу ON ERROR на ON ERROR UNDO, THROW для любого поддерживаемого процедурного блока, содержащегося в файле. Список поддерживаемых блоков включает:
- Процедура (также называемая main block, или внешняя процедура, или файл .p)
- Внутренняя процедура
- Триггер базы данных (Блок ON для события базы данных)
- Пользовательская функция
- Конструктор класса
- Пользовательский метод класса
- Функция доступа к свойству класса
Действие оператора не распространяется на деструкторы, так как деструкторы не могут устанавливать ошибку в вызывающем блоке. Оператор не влияет на блоки DO, FOR или REPEAT внутри процедурного блока и на триггерные блоки ON для событий пользовательского интерфейса.
Оператор должен размещаться в файле процедуры или класса до любого определяющего или исполняемого оператора. Но может располагаться как перед, так и после оператора USING.
Когда блок процедурного уровня или триггер базы данных содержит оператор CATCH, который явно обрабатывает возникшую ошибку, она не распространяется по стеку вызовов (если блок CATCH не делает этого явно).
Не следует считать, что использование этого оператора позволяет вам определить единственный блок CATCH для данного типа ошибки в файле persistent процедуры или класса, и он будет обрабатывать все ошибки этого типа, возникающие во всех суб-процедурах persistent процедуры или методах класса. Оператор просто изменяет обработку по умолчанию для всех суб-процедур (методов) в файле так, что гарантируется передача всех не обработанных в суб-процедурах (методах) ошибок в соответствующий вызывающий блок. Для каждой суб-процедуры или метода Вы должны решить, обрабатывать ли ошибку в собственном блоке CATCH. В качестве альтернативы, Вы можете вообще не использовать локальных блоков CATCH, а обрабатывать все ошибки на уровне вызывающего блока. Это может быть полезно, если вызывающий блок содержит множественные вызовы внутренних процедур в persistent процедуре (методов в классе).
Пример.
Лучшей практикой в большинстве случаев будет обработка ошибок, особенно связанных с данными в базе, локально. Это означает, что каждый блок должен иметь достаточно блоков CATCH, чтобы обработать все ошибки, которые разумно ожидать в данном блоке. Это обычно улучшает читаемость и сопровождаемость кода.
Но, конечно, существуют многочисленные случаи, когда централизованная обработка ошибок более осмысленна. Например, предположим, Вы имеете модуль, содержащий множество блоков, которые могут вызывать один и тот же тип ошибки. В этом случае, если код, обрабатывающий ошибку, одинаков для всех блоков в модуле, следует обрабатывать ошибки централизованно.
Другой подход – предоставить локальные блоки CATCH, требующие специфичной обработки ошибок, связанных с логикой Вашего приложения, а все неожиданные системные ошибки обрабатывать централизованно на верхнем уровне.
Для создания приложения, использующего структурную обработку необработанных локально ошибок на верхнем уровне необходимо:
- Включить оператор ROUTINE-LEVEL ON ERROR UNDO, THROW во все файлы процедур и классов.
- Включить блок CATCH для интерфейса Progress.Lang.Error в блок стартовой процедуры.
- Для каждого базового блока определить, необходим ли в нем явный THROW.
Пример (Программа 49) демонстрирует соответствующие принципы проектирования.
Программа 49. Централизованная обработка ошибок
ROUTINE-LEVEL ON ERROR UNDO, THROW. PROCEDURE find1000: /* Ignore potential errors */ FIND FIRST Customer WHERE CustNum = 1000 NO-ERROR. END PROCEDURE. PROCEDURE find2000: FIND FIRST Customer WHERE CustNum = 2000. CATCH eSysError AS Progress.Lang.SysError: /* Take care of this error locally */ END CATCH. END PROCEDURE. PROCEDURE find3000: FIND FIRST Customer WHERE CustNum = 3000. END PROCEDURE. /* Main Startup Procedure Block */ RUN find1000. RUN find2000. RUN find3000. /* Won't execute */ MESSAGE "Application completed execution successfully." VIEW-AS ALERT-BOX BUTTONS OK. CATCH eAnyError AS Progress.Lang.Error: MESSAGE "Unexpected error occured..." SKIP "Logging information..." SKIP "Exiting application..." VIEW-AS ALERT-BOX BUTTONS OK. QUIT. END CATCH.
THROW и пользовательские функции
Пользовательские функции, определяемые оператором FUNCTION, возвращают значение указанного в заголовке функции типа. Для возврата значения используется оператор RETURN. Это делает невозможным использование RETURN ERROR так же, как это делается в других блоках.
RETURN ERROR в пользовательской функции не вызывает состояние ERROR в вызывающем блоке. Вместо этого, он устанавливает возвращаемое значение функции в неопределенное значение. Соответственно, Вы можете проверить вызываемую функцию на ошибку, проверив возвращаемое значение.
Структурная обработка ошибок предоставляет Вам дополнительные возможности обработки ошибок в пользовательских функциях.
Передача системных ошибок.
В модели структурной обработки ошибок вы можете установить ошибку в вызывающем блоке, передав ошибку из блока CATCH в блоке функции. Затем эта ошибка может быть обработана блоком CATCH в вызывающем блоке (Программа 50).
Программа 50. Системные ошибки в функциях – Пример 1
/* First technique for returning a system error from a user-defined function */ DEFINE VARIABLE FuncReturn AS LOGICAL NO-UNDO. FUNCTION ReturnSysError RETURNS LOGICAL: FIND FIRST Customer WHERE CustNum = 1000. CATCH mySysError AS Progress.Lang.SysError: UNDO, THROW mySysError. END CATCH. END FUNCTION. ASSIGN FuncReturn = ReturnSysError(). CATCH mySysError AS Progress.Lang.SysError: DISPLAY "Error message returned from function: " mySysError:GetMessage(1) FORMAT "X(60)" SKIP. END CATCH.
Вы можете усовершенствовать Ваши функции для использования этой техники, не меняя традиционной обработки ошибок в вызывающем блоке. Можно указать NO-ERROR при вызове функции, при этом AVM автоматически поместит информацию из объекта SysError в хэндлер ERROR-STATUS (Программа 51).
Программа 51. Системные ошибки в функциях – Пример 2
/* Second technique for returning a system error from a user-defined function*/ DEFINE VARIABLE FuncReturn AS LOGICAL NO-UNDO. FUNCTION ReturnSysError RETURNS LOGICAL: FIND FIRST Customer WHERE CustNum = 1000. CATCH mySysError AS Progress.Lang.SysError: UNDO, THROW mySysError. END CATCH. END FUNCTION. ASSIGN FuncReturn = ReturnSysError() NO-ERROR. IF ERROR-STATUS:ERROR THEN DISPLAY "Error message returned from function: " ERROR-STATUS:GET-MESSAGE(1) FORMAT "X(60)".
Оператор ROUTINE-LEVEL ON ERROR UNDO, THROW также может быть использован для изменения обработки ошибок всех пользовательских функций в файле (Программа 52).
Программа 52. Системные ошибки в функциях – Пример 3
/* Third technique for returning a system error from a user-defined function */ ROUTINE-LEVEL ON ERROR UNDO, THROW. DEFINE VARIABLE FuncReturn AS LOGICAL NO-UNDO. FUNCTION ReturnSysError RETURNS LOGICAL: FIND FIRST Customer WHERE CustNum = 1000. END FUNCTION. ASSIGN FuncReturn = ReturnSysError(). CATCH mySysError AS Progress.Lang.SysError: DISPLAY "Error message returned from function: " mySysError:GetMessage(1) FORMAT "X(60)" SKIP. END CATCH.
Передача ошибок приложения
Передача ошибок приложения из функций несколько отличается. В примере (Программа 53) Вы предоставляете конструктор в операторе UNDO – для создания объекта AppError. В вызывающем блоке вы извлекаете сообщение об ошибке из свойства ReturnValue объекта AppError.
Программа 53. Ошибки приложения в функциях – Пример 1
/* First technique for returning an application error from a user-defined function. */ DEFINE VARIABLE FuncReturn AS LOGICAL NO-UNDO. FUNCTION ReturnAppError RETURNS LOGICAL: FIND FIRST Customer WHERE CustNum = 1000. RETURN TRUE. CATCH anyError AS Progress.Lang.ProError: UNDO, THROW NEW Progress.Lang.AppError("Function ReturnAppError failed."). END CATCH. END FUNCTION. ASSIGN FuncReturn = ReturnAppError(). CATCH myAppError AS Progress.Lang.AppError: DISPLAY "Error message returned from function: " myAppError:ReturnValue FORMAT "X(60)". END CATCH.
В следующем примере (Программа 54) вновь используется опция NO-ERROR в вызове функции и традиционная обработка ошибок в вызывающем блоке. Если объект AppError существует и его свойство ReturnValue имеет непустое значение, оно всегда доступно с помощью функции RETURN-VALUE. Заметим также, что ERROR-STATUS:ERROR равен TRUE вследствие THROW.
Программа 54. Ошибки приложения в функциях – Пример 2
/* Second technique for returning an application error from a user-defined function. */ DEFINE VARIABLE FuncReturn AS LOGICAL NO-UNDO. FUNCTION ReturnAppError RETURNS LOGICAL: FIND FIRST Customer WHERE CustNum = 1000. RETURN TRUE. CATCH anyError AS Progress.Lang.ProError: UNDO, THROW NEW Progress.Lang.AppError("Function ReturnAppError failed."). END CATCH. END FUNCTION. ASSIGN FuncReturn = ReturnAppError() NO-ERROR. IF ERROR-STATUS:ERROR THEN DISPLAY "Error message returned from function: " RETURN-VALUE FORMAT "X(60)".