Обработка исключений



Обработка исключений

Обработка исключений — это то, что происходит, когда система выдает ошибку.

Во время выполнения программы программа может столкнуться с ошибками. Иногда ошибки вносятся программистами случайно, но большинство ошибок связано с неправильным обращением с приложением. Ошибочные ситуации можно предвидеть во время разработки приложения, но просто невозможно избежать всех типов ошибок.

Обзор исключений

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

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

Процедурная обработка исключений

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

Поддержка исключений с помощью функциональных модулей

Исключения статически поддерживаются в функциональном модуле с пояснительным текстом, объясняющим ситуацию исключения. Когда возникает это исключение, системное поле SY-SUBRC устанавливается с номером исключения, который вызывающая программа может проверить, чтобы реализовать пользовательскую логику обработки ошибок.

Функциональный модуль Z_TEST_EXCEPTION_EXAMPLE поддерживает два исключения: NOT_FOUND и NO_AIRLINE_DATA. При вызове этого функционального модуля в функциональном модуле возникает соответствующее исключение, если возникает ситуация ошибки. Вызывающая программа проверяет значение SY-SUBRC, чтобы идентифицировать исключение и поддерживать соответствующую логику обработки ошибок в программе.




DATA lt_spfli TYPE spfli_tab.
CALL FUNCTION 'Z_TEST_EXCEPTION_EXAMPLE'
  EXPORTING
    iv_id        = 'LH'
  IMPORTING
    et_itab      = lt_spfli
  EXCEPTIONS
    NOT_FOUND       = 1
    NO_AIRLINE_DATA = 2
    OTHERS          = 3.
CASE sy-subrc.
  WHEN 1.
* Обработка исключения
* NOT_FOUND
  WHEN 2.
* Обработка исключения
* NO_AIRLINE_DATA  
  WHEN OTHERS.
* Обработка других исключений
ENDCASE.
Эти самоопределяемые исключения поддерживаются вручную в функциональных модулях и методах и вызываются с помощью оператора RAISE внутри функционального модуля или метода.

Когда возникает первое исключение, функциональный модуль закрывается, и управление передается обратно вызывающей программе без выполнения дополнительного кода в функциональном модуле. Если вызывающая программа не перехватывает исключение, генерируется ошибка времени выполнения (короткий дамп). По этой причине всегда рекомендуется использовать ключевое слово EXCEPTIONS в вызове функционального модуля (CALL FUNCTION) для перехвата исключений, даже если вы не планируете реализовывать какую-либо пользовательскую логику для исключения.

Поддержка исключений с помощью методов

Подобно функциональным модулям, исключения можно поддерживать в методах с помощью Class Builder, удерживая курсор на методе и выбирая кнопку "Исключение", как показано ниже.


В столбце «Исключение» на вкладке «Метод» дайте имя исключению и сохраните описание, объясняющее исключение, чтобы разработчики, вызывающие метод, могли понять, почему возникает это исключение.


Как показано, в методе можно вызвать исключение с помощью оператора RAISE
.

Обратите внимание, что исключения специфичны для процедуры в том смысле, что если исключение поддерживается для функционального модуля, оно может быть вызвано только внутри этого функционального модуля и не может быть вызвано другими функциональными модулями функциональной группы. Точно так же исключение, поддерживаемое для метода, может быть вызвано только внутри этого метода и не может быть вызвано другими методами класса.

Исключений для локальных классов

Для локальных классов исключения можно определить с помощью оператора EXCEPTIONS в части определения класса, как показано ниже. Исключения, определенные в части определения класса, могут быть вызваны методами, реализованными в части реализации того же класса.

CLASS lcl_test_class DEFINITION
  PUBLIC.

  PUBLIC SECTION.
    CLASS-METHODS read_spfli_into_table
      IMPORTING
        VALUE(iv_id)   TYPE spfli-carrid DEFAULT 'LH '
      EXPORTING
        VALUE(et_itab) TYPE spfli_tab
      EXCEPTIONS
        not_found         "Запись не найдена
        no_airline_data . "Нет данных авиакомпании

ENDCLASS.
Мы определили исключение NOT_FOUND в части определения класса LCL_TEST_CLASS. Исключения определяются с помощью ключевого слова EXCEPTIONS. Рекомендуется поддерживать комментарий с кратким описанием исключения.

Обработка исключений на основе классов

Исключения на основе классов (объектно-ориентированные) помогают отделить логику обработки исключений от основной логики программирования, обрабатывая исключения в отдельном блоке. В предыдущем разделе, посвященном процедурным методам обработки исключений, вы видели, что помимо проверки системного поля SY-SUBRC для идентификации исключения, информации об исключении не так много.

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

Один из способов — передать структуру ошибки или какую-либо переменную в интерфейс параметров и проверить ее после выполнения подпрограммы, как показано ниже.


DATA: lv_carrid  TYPE scarr-carrid,
      lt_spfli   TYPE spfli_tab,
      lv_retcode.

PERFORM read_spfli_into_table USING    lv_carrid
                              CHANGING lt_spfli
                                       lv_retcode.
IF lv_retcode IS INITIAL.
*Логика программы
ELSE.
*Логика обработки ошибок
ENDIF.

FORM read_spfli_into_table USING    iv_id
                           CHANGING ct_itab TYPE spfli_tab
                                    cv_retcode.

    SELECT SINGLE carrid INTO @DATA(lv_carrid) FROM scarr  WHERE carrid = @iv_id.
    IF sy-subrc NE 0.
      cv_retcode = 1.
    ENDIF.
    SELECT * FROM spfli WHERE carrid = @iv_id INTO TABLE @ct_itab.
    IF sy-subrc NE 0.
      cv_retcode = 2.
    ENDIF.
ENDFORM.
Как вы можете видеть, код использует параметр LV_RETCODE, чтобы зафиксировать ситуацию с ошибкой и соответствующим образом построить логику программы для обработки исключения. Однако это не самый элегантный способ обработки исключений, и интерфейс может вскоре стать загроможденным, если необходимо обработать множество исключительных ситуаций. Это также создает проблемы при пересылке исключения, если исключение необходимо обработать в другом блоке обработки. Например, большинство BAPI реализуют структуру ошибок в интерфейсе для захвата ошибки. В этой структуре ошибки фиксируется большая часть информации об ошибке, например тип ошибки, идентификатор ошибки, сообщение и т. д.

Теперь мы хотим передать эти сведения об ошибке другому функциональному модулю, вызываемому в BAPI, в котором может быть обработано исключение. Для этого нам нужно убедиться, что BAPI и функциональный модуль, вызываемый в BAPI, имеют схожие структуры ошибок; в противном случае нам нужно вручную преобразовать данные и присвоить их новой структуре ошибок функционального модуля.

Вопреки тому, что предполагают многие разработчики, исключения на основе классов не ограничиваются объектами ABAP. Их можно использовать в любом блоке обработки. Исключения на основе классов обрабатываются с помощью специального блока, называемого блоком TRY. Используя операторы CATCH и CLEANUP, мы можем обрабатывать исключения в блоке TRY. Оператор CATCH может перехватывать несколько исключений внутри блока, а оператор CLEANUP позволяет выполнять задачи очистки при выходе из исключения. Например, если вы открываете файл для вывода и сталкиваетесь с исключением при передаче одной из записей, то блок очистки позволяет закрыть файл.

Raising Exceptions

Исключения возникают в нескольких ситуациях — либо программно с помощью оператора RAISE EXCEPTION, либо автоматически средой выполнения ABAP. Например, если мы выполним код ниже, это приведет к ошибке времени выполнения.
DATA : lv_unit_value  TYPE i,
       lv_total_value TYPE i VALUE 20,
       lv_quantity    TYPE i.
lv_unit_value = lv_total_value / lv_quantity.

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

Ниже показан синтаксис реализации блока TRY для обработки исключений.

DATA lx_ex TYPE REF TO cx_sy_zerodivide.
DATA : lv_unit_value  TYPE i,
       lv_total_value TYPE i VALUE 20,
       lv_quantity    TYPE i.
TRY. "Начало блока TRY
    lv_unit_value = lv_total_value / lv_quantity.
*Другая логика программирования
  CATCH cx_sy_zerodivide INTO lx_ex.
    WRITE :/'Короткий текст ошибки:' , lx_ex->get_text( ).
    WRITE :/'Длинный текст ошибки:' , lx_ex->get_longtext( ).
  CLEANUP.
* Любая логика очистки
ENDTRY. "Конец TRY блока
Код приводит к выводу, показанному ниже. Здесь система не генерировала короткий дамп, потому что мы поймали исключение в программе и обработали исключительную ситуацию программно. Хорошая практика программирования состоит в том, чтобы избегать таких исключительных ситуаций, проверяя знаменатель перед его использованием при делении (например, IF lv_quantity IS NOT INITIAL ).

Перехватываемые и неперехватываемые исключения

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

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

Возвращаясь к коду, мы определили перехватываемое исключение CX_SY_ZERODIVIDE, которое вызывается системой, когда она сталкивается со значением, делящимся на ноль. Единственная ситуация, в которой системное исключение не возникает при таких обстоятельствах, — это когда числитель также равен нулю. Здесь CX_SY_ZERODIVIDE — это глобальный класс с определенными компонентами.


Как видно, класс CX_SY_ZERODIVIDE реализует интерфейс IF_MESSAGE с двумя методами: GET_TEXT и GET_LONGTEXT. Псевдонимы для этих методов сохраняются на вкладке Псевдонимы.

Мы определили ссылочный объект LX_ER, ссылающийся на статический тип CX_SY_ZERODIVIDE. В управляющей структуре TRY оператор CATCH cx_sy_zerodivide INTO lx_ex создает экземпляр объекта LX_EX. Мы получили доступ к короткому и полному тексту ошибки, вызвав методы GET_TEXT и GET_LONGTEXT соответственно.

При желании мы также сохранили блок CLEANUP, в котором может храниться подходящий код выхода для корректного выхода из процедуры. Например, блок CLEANUP можно использовать для закрытия любых открытых файлов или отката любых обновлений базы данных.

Если вы внимательно посмотрите на класс CX_SY_ZERODIVIDE, то увидите, что он наследуется от суперкласса CX_SY_ARITHMETIC_ERROR. Класс CX_SY_ARITHMETIC_ERROR, в свою очередь, наследуется от суперкласса CX_DYNAMIC_CHECK, который наследуется от CX_ROOT

Базовым классом для всех классов исключений является CX_ROOT, который реализует интерфейсы IF_MESSAGE и IF_SERIALIZABLE_OBJECT. Интерфейс IF_MESSAGE содержит методы GET_TEXT и GET_LONGTEXT, позволяющие извлечь сообщение об ошибке. Интерфейс IF_SERIALIZABLE_OBJECT используется для сериализации объектов — процесса, в котором объект преобразуется в формат сериализации (например, XML, CSV), который может быть передан для создания клона объекта в той же или другой системе. Интерфейс IF_SERIALIZABLE_OBJECT должен быть реализован, если объект исключения необходимо сериализовать. Класс CX_ROOT также реализует другой метод, GET_SOURCE_position, который возвращает позицию в исходном коде, вызвавшую ошибку.

При перехвате исключений на основе классов информация об исключении захватывается объектом, который является экземпляром класса, наследуемого от одного из трех глобальных абстрактных классов: CX_STATIC_CHECK, CX_DYNAMIC_CHECK или CX_NO_CHECK. Эти три глобальных абстрактных класса, в свою очередь, являются подклассами CX_ROOT. Например, мы можем определить ссылочный объект LX_ER как экземпляр класса CX_ROOT вместо класса CX_SY_ZERODIVIDE и по-прежнему иметь возможность перехватывать исключение, как показано в листинге 9.6. Однако это сделает логику ошибки общей и упустит какие-либо конкретные детали обработки исключений, поддерживаемые в конкретном классе исключений.

Как уже отмечалось, при определении пользовательского класса исключений он должен наследоваться от одного из трех классов:

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

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

CX_NO_CHECK
Класс исключения, который является подклассом CX_NO_CHECK, не может быть определен в сигнатуре функционального модуля или метода. Он неявно объявлен по умолчанию и всегда передается вызывающей стороне.


Поскольку CX_STATIC_CHECK проверяется компилятором во время разработки, рекомендуется использовать этот класс для определения классов исключений. Большинство системных исключений являются производными от CX_DYNAMIC_CHECK.

Глобальное определение классов исключений

Чаще всего мы определяем классы исключений глобально, чтобы их можно было использовать в нескольких программах. Действия по созданию класса исключения в Class Builder аналогичны действиям по созданию любого обычного класса, за исключением того, что класс исключения должен наследовать CX_STATIC_CHECK, CX_DYNAMIC_CHECK или CX_NO_CHECK или любой класс, наследующий эти классы.

Кроме того, классы исключений должны следовать соглашению об именах CX_ для пространства имен SAP и ZCX_ для пространства имен клиента. Флажок "Класс исключения" также установлен для класса исключения.

Флажок "С классом сообщений" позволит вам использовать класс сообщений для хранения сообщений.

Вы заметите, что Class Builder создаст класс исключений, которая похожа на обычный класса, за исключением незначительных дополнительных опций. Например, вы увидите вкладку «Тексты», которая недоступна при создании обычных классов.

SAP рекомендует не определять новые методы и не переопределять унаследованные методы.

Исключение можно вызвать внутри функционального модуля, используя синтаксис RAISE EXCEPTION TYPE класс_исключений.

Локальное определение классов исключений

Локальные классы исключений могут быть определены для локальной работы с исключениями внутри программы. Синтаксис определения локального класса исключений подобен обычному классу, за исключением того, что он должен наследовать CX_STATIC_CHECK, CX_DYNAMIC_CHECK или CX_NO_CHECK. Соглашение об именах LCX_ используется для локальных классов исключений.

Помимо их видимости, работа с локальными классами исключений аналогична работе с глобальными классами исключений.

Сообщения в классах исключений

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

Тексты могут храниться непосредственно в классе исключений, который использует онлайн-репозиторий текстов (OTR), или мы также можем использовать сообщения из класса сообщений (транзакция SE91).

Использование онлайн-репозитория текстов

Следующие шаги помогут вам поддерживать сообщения в классах исключений:

1. На вкладке Атрибуты класса исключений определите атрибут со значением, которое вы хотите передать в сообщении. В этом примере мы поддерживаем MV_CARRID в качестве имени атрибута типа S_CARR_ID, поскольку мы планируем передать краткое название авиакомпании в сообщении об исключении. После определения атрибута нажмите «Активировать».


2. На вкладке «Тексты» сохраните идентификатор исключения и текст. Вы можете передать значение атрибута в сообщении, сохранив имя атрибута между амперсандами (&). Здесь мы сохранили идентификатор исключения как INVALID_CARRID, а текст как Нет данных авиакомпании &MV_CARRID&, заключив атрибут в амперсанды. Вы также можете сохранить длинный текст сообщения, нажав кнопку «Длинный текст». Когда закончите, нажмите Активировать.


3. В этот момент на вкладке «Атрибуты» вы увидите, что определенный идентификатор исключения добавляется как константа типа SOTR_CONC. Поддерживаемый текст сохраняется в OTR и поддерживает интернационализацию (переводы).

4. Конструктор класса автоматически добавляет атрибуты, которые вы добавили в качестве параметров импорта, в свою сигнатуру, которую можно передать при возникновении исключения с помощью ключевого слова EXPORTING. Конструктор гарантирует, что все атрибуты инициализированы правильно. Атрибут textid хранит идентификатор исключения для доступа к соответствующему тексту. Предыдущий атрибут хранит предыдущие исключения в контексте. Ниже показаны сигнатура и код инициализации метода конструктора после добавления атрибута MV_CARRID.

5. Чтобы сгенерировать исключение и передать сообщение с названием авиакомпании, вы можете написать оператор RAISE EXCEPTION, как показано на ниже. Здесь вы вызываете конструктор и передаете TEXTID, обращаясь к константе INVALID_CARRID, определенной в атрибутах класса. Эта константа относится к соответствующему идентификатору исключения. Вы также передаете номер материала атрибуту MV_CARRID, чтобы поле &MV_CARRID& в тексте было заменено значением.



Перехват исключения в программе.
 
DATA lt_spfli TYPE spfli_tab.
TRY.
    CALL FUNCTION 'Z_TEST_EXCEPTION_EXAMPLE'
      EXPORTING
        iv_id   = 'LR'
      IMPORTING
        et_itab = lt_spfli.
  CATCH zcx_test_exception INTO DATA(lx_msg).
    WRITE / lx_msg->get_text( ).
ENDTRY.

Использование сообщений из класса сообщений

Чтобы использовать сообщения из класса сообщений, выполните следующие действия:
1. Установите флажок «С классом сообщения». Как только этот флажок установлен, система автоматически добавляет к классу сообщений интерфейсы IF_T100_MESSAGE, IF_T100_DYN_MSG, который используется для доступа к сообщениям, хранящимся в классе сообщений.
    


2.Чтобы использовать сообщение из класса сообщений, перейдите на вкладку «Тексты», сохраните идентификатор исключения и нажмите кнопку «Текст сообщения».

3. В окне «Назначить атрибуты класса исключений сообщению» укажите имя класса сообщений и идентификатор сообщения (номер сообщения). Если вы хотите передать значение атрибута в сообщение, вы можете выбрать атрибут из раскрывающегося списка рядом с каждым полем атрибута. Вы можете передать до четырех атрибутов.


4. Чтобы сгенерировать исключение и передать сообщение с названием авиакомпании, вы можете написать оператор RAISE EXCEPTION.

Перехват исключения в программе.
DATA: lt_spfli TYPE spfli_tab,
      lv_id    TYPE s_carr_id VALUE 'LR'.

DO 2 TIMES.
  TRY.
      CALL FUNCTION 'Z_TEST_EXCEPTION_EXAMPLE'
        EXPORTING
          iv_id   = lv_id
        IMPORTING
          et_itab = lt_spfli.
    CATCH cx_static_check INTO DATA(lx_msg).
      WRITE / lx_msg->get_text( ).
  ENDTRY.
  lv_id = 'AB'.
ENDDO.
Использовали вышестоящий класс исключений cx_static_check, для обоих классов исключений.
Результат:

Использование дополнения MESSAGE для создания исключения

В SAP NetWeaver 7.5 появилось новое дополнение MESSAGE, которое можно использовать при возникновении исключений. Используя дополнение MESSAGE, вы можете передать спецификации сообщения, такие как тип сообщения, идентификатор сообщения и номер сообщения, а также заполнители для сообщения.

Если класс сообщения реализует интерфейс IF_T100_DYN_MSG, вы можете передать тип сообщения, используя дополнение TYPE, а заполнители для сообщения можно передать, используя дополнение WITH. Однако, если класс сообщения реализует интерфейс IF_T100_MESSAGE, то вы не можете передать тип сообщения
и заполнители сообщений, использующие дополнение WITH.
  RAISE EXCEPTION TYPE zcx_test_exception_message
      MESSAGE ID sy-msgid
      TYPE sy-msgty
      NUMBER sy-msgno
      WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
В пример показан кода для создания исключения, когда интерфейс IF_T100_DYN_MSG реализован в классе сообщения. В этом примере мы используем дополнение MESSAGE для передачи спецификаций сообщения. Атрибуты и тип сообщения можно передавать напрямую, не определяя их отдельно в классе исключений, поскольку интерфейс IF_T100_DYN_MSG обеспечивает встроенную поддержку.




Комментарии