Что умеет Try Catch в ABAP?
Привет, сейчас мы коснёмся лучшего языка программирования ABAP, ещё бы. А точнее возможностей TRY...CATCH в разрезе данного языка. Вроде что-то «пытается» , что-то «ловит», но не всё так очевидно...
У данной конструкции открываются дополнительные возможности, если добавить такие операторы, как CLEANUP, RESUME, RETRY и BEFORE UNWIND. Обсудим для чего они нужны и как с ними работать.
CLEANUP
Блок CLEANUP
будет использоваться для удаления ссылок перед завершением вызова метода. Всякий раз, когда возникает исключение, система прекращает обработку с этого момента и переходит к соответствующему блоку TRY
. Из-за такого поведения объект будет находиться в промежуточном состоянии. Блок CLEANUP
предоставляет нам возможность восстановить состояние объекта перед выходом из текущего блока обработки.
CLASS lcl_main DEFINITION FINAL.
PUBLIC SECTION.
METHODS:
method1,
method2.
ENDCLASS.
CLASS lcl_main IMPLEMENTATION.
METHOD method1.
TRY.
method2( ).
CATCH cx_root.
cl_demo_output=>write( 'catching cx_root' ).
ENDTRY.
cl_demo_output=>display( ).
ENDMETHOD.
METHOD method2.
TRY.
DATA(result) = 2 / 0.
CLEANUP.
cl_demo_output=>write( 'cleanup' ).
ENDTRY.
ENDMETHOD.
ENDCLASS.
START-OF-SELECTION.
NEW lcl_main( )->method1( ).
В этом примере у нас есть «внешний» блок TRY..ENDTRY
в методе method1. У нас есть «внутренний» блок TRY..ENDTRY
в методе method2, который запускается из внешнего блока. Поскольку мы делим на 0, оператор деления вызовет исключение типа CX_SY_ZERODIVIDE
. Поскольку мы перехватываем все исключения, используя CATCH CX_ROOT
во внешнем блоке TRY
, система выполнит логику блока CLEANUP
во внутреннем блоке TRY
.
Замените этот CX_ROOT
на CX_SY_BUFFER_OVERFLOW
, который не является частью иерархии CX_SY_ZERODIVIDE
, поэтому CLEANUP
не будет выполнена, и это приведет к дампу во время выполнения.
RESUME
С RESUME
вы выходите из блока CATCH
и возобновляете обработку после инструкции, вызвавшей исключение. Пример ниже отлично демонстрирует возможности RESUME
.
CLASS lcl_employee DEFINITION FINAL CREATE PUBLIC.
PUBLIC SECTION.
TYPES: BEGIN OF empl_data,
empid TYPE int4, " Employee ID
emptyp TYPE string, " Org Assignment data
salary TYPE decfloat16, " Pay data
phone TYPE numc10, " Communication data
END OF empl_data,
empl_data_t TYPE SORTED TABLE OF empl_data WITH UNIQUE KEY empid.
METHODS constructor IMPORTING VALUE(i_empid) TYPE int4.
METHODS get_data RETURNING VALUE(rs_result) TYPE empl_data
RAISING RESUMABLE(cx_no_data_found).
PRIVATE SECTION.
DATA emp_id TYPE int4.
METHODS get_emptyp RETURNING VALUE(r_result) TYPE string
RAISING cx_no_data_found.
METHODS get_salary RETURNING VALUE(r_result) TYPE decfloat16
RAISING RESUMABLE(cx_no_data_found).
METHODS get_phone RETURNING VALUE(r_result) TYPE numc10.
METHODS get_emp_id RETURNING VALUE(r_result) TYPE int4.
ENDCLASS.
CLASS lcl_employee IMPLEMENTATION.
METHOD constructor.
me->emp_id = i_empid.
ENDMETHOD.
METHOD get_data.
rs_result = VALUE #( empid = me->get_emp_id( )
emptyp = me->get_emptyp( )
salary = me->get_salary( )
phone = me->get_phone( ) ).
ENDMETHOD.
METHOD get_emptyp.
r_result = SWITCH #( me->get_emp_id( )
WHEN 1 THEN |Full-Time|
WHEN 2 THEN |Part-Time|
WHEN 3 THEN |Contractor|
WHEN 4 THEN |Casual|
ELSE THROW cx_no_data_found(
rel_proc_id = CONV #( me->get_emp_id( ) ) ) ).
ENDMETHOD.
METHOD get_phone.
r_result = SWITCH #( me->get_emptyp( )
WHEN `Full-Time` THEN |1234567890|
WHEN `Part-Time` THEN |5678901234|
WHEN `Casual` THEN |7890123456|
ELSE |0399999999| ).
ENDMETHOD.
METHOD get_salary.
r_result = SWITCH #( me->get_emptyp( )
WHEN `Full-Time` THEN 50000
WHEN `Part-Time` THEN 25000
WHEN `Casual` THEN 5000
ELSE THROW RESUMABLE cx_no_data_found(
rel_proc_id = CONV #( me->get_emp_id( ) ) ) ).
ENDMETHOD.
METHOD get_emp_id.
r_result = me->emp_id.
ENDMETHOD.
ENDCLASS.
DATA extract_t TYPE lcl_employee=>empl_data_t.
DATA error_t TYPE string_table.
START-OF-SELECTION.
DATA(all_employees_t) = VALUE int4_table( ( 1 ) ( 2 ) ( 3 ) ( 4 ) ( 5 ) ).
LOOP AT all_employees_t REFERENCE INTO DATA(dref).
TRY.
INSERT NEW lcl_employee( dref->* )->get_data( ) INTO TABLE extract_t.
CATCH BEFORE UNWIND cx_no_data_found INTO DATA(no_data_error).
IF no_data_error->is_resumable = abap_true.
" Вызванное возобновляемое исключение
RESUME.
CLEANUP INTO DATA(lv_clear).
ELSE.
" Вызванное не возобновляемое исключение
error_t = VALUE #( BASE error_t ( no_data_error->get_text( ) ) ).
ENDIF.
ENDTRY.
ENDLOOP.
cl_demo_output=>new( )->write( extract_t )->write( error_t )->display( ).
Благодаря RESUME
в методе получения зарплаты, когда зарплата не была найдена, в таком случае нам надо сообщить об ошибке и добавить запись. Если убрать RESUME
то после вызова исключения, запись в таблицу добавленна не будет.
Не обращайте внимание на BEFORE UNWIND
, о нём мы поговорим чуть позже.
RETRY
С RETRY
вы выходите из блока CATCH
и прыгаете обратно в блок TRY
структура управления для того, чтобы повторить полный блок TRY
. Конечно, вы должны позаботиться о том, чтобы исключение не повторялось снова и снова, иначе вы попадете в бесконечный цикл.
CLASS lcl_main DEFINITION FINAL.
PUBLIC SECTION.
METHODS:
method1,
method2.
ENDCLASS.
CLASS lcl_main IMPLEMENTATION.
METHOD method1.
TRY.
method2( ).
CATCH BEFORE UNWIND cx_root.
cl_demo_output=>write( 'catching cx_root' ).
ENDTRY.
cl_demo_output=>display( ).
ENDMETHOD.
METHOD method2.
DATA(index) = 0.
TRY.
DATA(result) = 2 / index.
CATCH cx_sy_zerodivide.
cl_demo_output=>write( 'catching cx_sy_zerodivide' ).
index = 1.
cl_demo_output=>write( 'catching retry' ).
RETRY.
ENDTRY.
ENDMETHOD.
ENDCLASS.
START-OF-SELECTION.
NEW lcl_main( )->method1( ).
BEFORE UNWIND
Если указано дополнение BEFORE UNWIND
, контекст, в котором исключение был вызван, включая все вызванные процедуры и их локальные данные, удаляется только после выхода из CATCH
блок. Если добавление не указано, контекст удаляется до выполнения CATCH
блока.
Если дополнение
BEFORE UNWIND
указано, операторRESUME
может быть использован вCATCH
блоке для обработки возобновляемого исключения, чтобы возобновить обработку после инструкции, которая вызвала исключение. Это единственный случай, когда контекст исключения не удаляется при выходе изCATCH
блока.Возобновляемые исключения также могут обрабатываться
CATCH
блоками без добавленияBEFORE UNWIND
. В этом случае контекст исключения удаляется перед процессом обработки, и операторRESUME
не может быть указан.Любые
[CLEANUP](<https://eduardocopat.github.io/abap-docs/7.40/abapcleanup>)
блоки всегда выполняется непосредственно перед удалением их контекста. ЕслиBEFORE UNWIND
используется, то после обработки исключений и во всех остальных случаях до обработки исключений.Использование дополнения
BEFORE UNWIND
дляCATCH
требуется только тогда, когда заявление[RESUME](<https://eduardocopat.github.io/abap-docs/7.40/abapresume>)
используется. Однако это в принципе разрешено во время обработки исключений, если контекст исключения должен быть оценен перед любыми действиями по очистке вCLEANUP
блоки. Это имеет смысл, например, при обработке узких мест в ресурсах, если высвобождение ресурсов вCLEANUP
блоке изменили бы контекст и, таким образом, произвели бы вычисление, свободные ресурсы в обработчике исключений бессмысленны.