Привет, сейчас мы коснёмся лучшего языка программирования 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 блоке изменили бы контекст и, таким образом, произвели бы вычисление, свободные ресурсы в обработчике исключений бессмысленны.