Перейти до вмісту

Винятки

Виняток (exception) — штатний спосіб сигналізувати про ненормальну ситуацію: бракує даних, помилка конвертації, відмова зовнішнього сервісу. У сучасному ABAP використовуються class-based exceptions — усі спадкуються від cx_root. Старі sy-subrc-based винятки у function modules (EXCEPTIONS ... = ...) — legacy, не для нового коду.

Ключова ідея: метод, що може впасти, декларує це у RAISING; викликач або обробляє через TRY/CATCH, або пробрасує далі. Компілятор перевіряє цей контракт для cx_static_check.

Клас-базаПеревіркаКоли використовувати
cx_static_checkКомпілятор вимагає RAISING або TRY/CATCHОчікувані помилки, які викликач має свідомо обробити (валідація, бракує запису)
cx_dynamic_checkЛише в runtimeСитуації, яких у нормальному потоці не має бути (інваріант, арифметичні переповнення)
cx_no_checkНе потребує деклараціїСистемні критичні; використовуй дуже рідко у власних класах
CLASS zcx_not_found DEFINITION
PUBLIC
INHERITING FROM cx_static_check
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_t100_dyn_msg. " для інтеграції з T100-повідомленнями
INTERFACES if_t100_message.
CONSTANTS:
BEGIN OF no_data,
msgid TYPE symsgid VALUE 'ZDEMO',
msgno TYPE symsgno VALUE '001',
attr1 TYPE scx_attrname VALUE 'ID',
END OF no_data.
METHODS constructor
IMPORTING textid LIKE if_t100_message=>t100key OPTIONAL
previous LIKE previous OPTIONAL
id TYPE string OPTIONAL.
DATA id TYPE string.
ENDCLASS.
CLASS zcx_not_found IMPLEMENTATION.
METHOD constructor.
super->constructor( previous = previous ).
me->id = id.
IF textid IS INITIAL.
if_t100_message~t100key = no_data.
ELSE.
if_t100_message~t100key = textid.
ENDIF.
ENDMETHOD.
ENDCLASS.

Ключові моменти:

  • Спадкування від cx_static_check / cx_dynamic_check / cx_no_check визначає категорію.
  • super->constructor( previous = previous ) — пробрасування попереднього винятку (exception chaining).
  • Для текстів повідомлень використовуй if_t100_message (TOBJECT-повідомлення) або IF_T100_DYN_MSG (з параметрами).

Викидання — RAISE EXCEPTION / THROW

Section titled “Викидання — RAISE EXCEPTION / THROW”
" Форма statement
RAISE EXCEPTION TYPE zcx_not_found
EXPORTING id = `42`.
" Forma виразу — всередині constructor expression
result = COND #( WHEN id IS INITIAL
THEN THROW zcx_not_found( id = `empty` )
ELSE load( id ) ).
" З текстом повідомлення
RAISE EXCEPTION TYPE zcx_not_found
MESSAGE ID 'ZDEMO' TYPE 'E' NUMBER '001'
WITH id.
" Пробросити існуючий виняток (chaining)
TRY.
" ...
CATCH cx_sy_open_sql_db INTO DATA(db_exc).
RAISE EXCEPTION TYPE zcx_service_error EXPORTING previous = db_exc.
ENDTRY.

THROW — це RAISE EXCEPTION у формі виразу, зручно у COND/SWITCH.

TRY.
DATA(result) = service->load( id ).
process( result ).
CATCH zcx_not_found INTO DATA(e_nf).
log->warn( |Not found: { e_nf->id }| ).
" fallback
CATCH zcx_service_error cx_sy_open_sql_db INTO DATA(e_other).
log->error( e_other->get_text( ) ).
RAISE EXCEPTION TYPE zcx_my_error EXPORTING previous = e_other.
CATCH BEFORE UNWIND cx_root INTO DATA(e_any).
" catch-all на випадок чого завгодно
log->fatal( e_any->get_text( ) ).
RAISE. " прокинути як є
ENDTRY.

Правила:

  • Класи ловляться зверху вниз — перший збіг спрацьовує. Більш специфічні класи вище.
  • Кілька класів у одному CATCH через пробіл/кому.
  • INTO e — отримати обʼєкт винятку.
  • RAISE. без аргументів — перекинути поточний.

RESUMABLE — продовжити після виклику

Section titled “RESUMABLE — продовжити після виклику”

Якщо виняток викинуто з RESUMABLE, обробник може зробити RESUME і продовжити з наступного оператора після RAISE:

" Виклик:
RAISE RESUMABLE EXCEPTION TYPE zcx_warning.
" Обробник:
CATCH RESUMABLE zcx_warning INTO DATA(w).
log->warn( w->get_text( ) ).
RESUME. " повернутись і продовжити

Використовується рідко — у кастомних фреймворках. Зазвичай вистачає нормального flow без RESUME.

Код, що виконається при виході з TRY через необроблений виняток:

TRY.
open_file( ).
process_file( ).
CATCH zcx_parse_error.
" ...
CLEANUP.
close_file( ). " виконається якщо виняток не піймався локально
ENDTRY.

RETRY у CATCH — повторює весь блок TRY. Використовується для ретраїв мережевих викликів:

DATA attempts TYPE i.
TRY.
call_external_api( ).
CATCH cx_http_timeout.
attempts += 1.
IF attempts < 3.
WAIT UP TO 1 SECONDS.
RETRY.
ENDIF.
RAISE.
ENDTRY.
" Людино-читабельний
DATA(text) = e->get_text( ).
" Long text (якщо налаштовано у OTR)
DATA(long) = e->get_longtext( ).
" Ідентифікатор
DATA(source) = e->get_source_position( ).

Повідомлення T100 як виняткові тексти

Section titled “Повідомлення T100 як виняткові тексти”

Інтеграція з класичними Message Classes (SE91) — виняток несе MSGID/MSGNO/привʼязку змінних:

CLASS zcx_demo DEFINITION INHERITING FROM cx_static_check.
PUBLIC SECTION.
INTERFACES if_t100_dyn_msg.
CONSTANTS:
BEGIN OF invalid_id,
msgid TYPE symsgid VALUE 'ZDEMO',
msgno TYPE symsgno VALUE '010',
attr1 TYPE scx_attrname VALUE 'ID',
END OF invalid_id.
DATA id TYPE string.
ENDCLASS.
" Викинути зі змінною у повідомленні
RAISE EXCEPTION TYPE zcx_demo
MESSAGE ID 'ZDEMO' TYPE 'E' NUMBER '010' WITH id
EXPORTING textid = zcx_demo=>invalid_id id = `X1`.

Класичні винятки у function modules

Section titled “Класичні винятки у function modules”

Старий стиль — через EXCEPTIONS:

CALL FUNCTION 'SOME_FM'
EXPORTING input = x
EXCEPTIONS not_found = 1
error = 2
OTHERS = 3.
CASE sy-subrc.
WHEN 1. " not_found
WHEN 2. " error
ENDCASE.

Не використовуй у новому коді — загортай function modules у методи з class-based винятками.

Runtime error (ABAP short dump) виникає, коли:

  • Необроблений виняток cx_no_check або не піймане cx_dynamic_check.
  • Явне RAISE SHORTDUMP TYPE ....
  • Порушення ABAP-контракту (ділення на нуль у цілих, розмір/тип не збігається, неприпустимий доступ до памʼяті).

Кидати short dump власноруч — вкрай рідко, лише там де продовжити неможливо:

RAISE SHORTDUMP TYPE zcx_fatal EXPORTING reason = `impossible state`.
" Якщо умова false — runtime error
ASSERT x > 0.
ASSERT id IS NOT INITIAL.

ASSERT — для інваріантів, які ніколи не мають зламатися в нормальному коді. Це маркер “тут щось глибоко не так”. Не використовуй для валідації бізнес-вводу — там виняток.

Чужий низькорівневий виняток загортаємо у свій семантичний, зберігаючи ланцюг:

TRY.
SELECT SINGLE * FROM ztab WHERE id = @id INTO @DATA(row).
CATCH cx_sy_open_sql_db INTO DATA(db).
RAISE EXCEPTION TYPE zcx_persistence_error
EXPORTING previous = db.
ENDTRY.

Викликач бачить семантичний zcx_persistence_error, але через previous може дістатись до причини.

METHOD save.
IF customer-id IS INITIAL.
RAISE EXCEPTION TYPE zcx_validation EXPORTING field = `id`.
ENDIF.
IF strlen( customer-email ) > 100.
RAISE EXCEPTION TYPE zcx_validation EXPORTING field = `email`.
ENDIF.
" ...
ENDMETHOD.

Перетворення sy-subrc у виняток

Section titled “Перетворення sy-subrc у виняток”
SELECT SINGLE * FROM ztab WHERE id = @id INTO @DATA(row).
IF sy-subrc <> 0.
RAISE EXCEPTION TYPE zcx_not_found EXPORTING id = id.
ENDIF.
  • Створюй свої класи-спадкоємці cx_static_check.
  • Оголошуй RAISING у методах.
  • Ловиш конкретне, не cx_root без причини.
  • Зберігай previous — ланцюг врятує дебагера.
  • ASSERT — для інваріантів; виняток — для бізнесу.

Адаптовано з 27_Exceptions.md (Apache 2.0). Повний перелік нюансів — в оригіналі.