ABAP Exception Classes: Eigene Ausnahmen erstellen

kategorie
ABAP-Statements
Veröffentlicht
autor
Johannes

Exception Classes ermöglichen strukturierte Fehlerbehandlung in ABAP. Eigene Ausnahmen (CX_-Klassen) transportieren Fehlerdaten, unterstützen Mehrsprachigkeit und integrieren sich nahtlos in TRY-CATCH.

Exception-Hierarchie

CX_ROOT
├── CX_STATIC_CHECK " Muss deklariert werden (RAISING)
│ └── CX_DYNAMIC_CHECK " Kann, muss aber nicht deklariert werden
└── CX_NO_CHECK " Muss nicht deklariert werden (Runtime)
BasisklasseRAISING nötigTypischer Einsatz
CX_STATIC_CHECKJaBusiness-Exceptions
CX_DYNAMIC_CHECKOptionalTechnische Fehler
CX_NO_CHECKNeinKritische Systemfehler

Exception-Klasse erstellen

1. Einfache Exception (SE24/ADT)

CLASS zcx_validation_error DEFINITION
PUBLIC
INHERITING FROM cx_static_check
CREATE PUBLIC.
PUBLIC SECTION.
" Interface für Texte
INTERFACES: if_t100_message.
INTERFACES: if_t100_dyn_msg.
" Konstanten für Nachrichtentexte
CONSTANTS:
BEGIN OF invalid_input,
msgid TYPE symsgid VALUE 'ZMSG',
msgno TYPE symsgno VALUE '001',
attr1 TYPE scx_attrname VALUE 'MV_FIELD',
attr2 TYPE scx_attrname VALUE '',
attr3 TYPE scx_attrname VALUE '',
attr4 TYPE scx_attrname VALUE '',
END OF invalid_input.
" Attribute für Fehlerdaten
DATA: mv_field TYPE string READ-ONLY.
" Konstruktor
METHODS: constructor
IMPORTING
textid LIKE if_t100_message=>t100key OPTIONAL
previous LIKE previous OPTIONAL
iv_field TYPE string OPTIONAL.
ENDCLASS.
CLASS zcx_validation_error IMPLEMENTATION.
METHOD constructor.
super->constructor( previous = previous ).
mv_field = iv_field.
CLEAR me->textid.
IF textid IS INITIAL.
if_t100_message~t100key = invalid_input.
ELSE.
if_t100_message~t100key = textid.
ENDIF.
ENDMETHOD.
ENDCLASS.

2. Exception mit mehreren Texten

CLASS zcx_order_error DEFINITION
PUBLIC
INHERITING FROM cx_static_check
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES: if_t100_message.
" Verschiedene Fehlertexte
CONSTANTS:
BEGIN OF order_not_found,
msgid TYPE symsgid VALUE 'ZORDER',
msgno TYPE symsgno VALUE '001',
attr1 TYPE scx_attrname VALUE 'MV_ORDER_ID',
attr2 TYPE scx_attrname VALUE '',
attr3 TYPE scx_attrname VALUE '',
attr4 TYPE scx_attrname VALUE '',
END OF order_not_found,
BEGIN OF order_already_shipped,
msgid TYPE symsgid VALUE 'ZORDER',
msgno TYPE symsgno VALUE '002',
attr1 TYPE scx_attrname VALUE 'MV_ORDER_ID',
attr2 TYPE scx_attrname VALUE 'MV_SHIP_DATE',
attr3 TYPE scx_attrname VALUE '',
attr4 TYPE scx_attrname VALUE '',
END OF order_already_shipped,
BEGIN OF invalid_quantity,
msgid TYPE symsgid VALUE 'ZORDER',
msgno TYPE symsgno VALUE '003',
attr1 TYPE scx_attrname VALUE 'MV_QUANTITY',
attr2 TYPE scx_attrname VALUE 'MV_MAX_QUANTITY',
attr3 TYPE scx_attrname VALUE '',
attr4 TYPE scx_attrname VALUE '',
END OF invalid_quantity.
" Attribute
DATA: mv_order_id TYPE string READ-ONLY,
mv_ship_date TYPE d READ-ONLY,
mv_quantity TYPE i READ-ONLY,
mv_max_quantity TYPE i READ-ONLY.
METHODS: constructor
IMPORTING
textid LIKE if_t100_message=>t100key OPTIONAL
previous LIKE previous OPTIONAL
iv_order_id TYPE string OPTIONAL
iv_ship_date TYPE d OPTIONAL
iv_quantity TYPE i OPTIONAL
iv_max_quantity TYPE i OPTIONAL.
ENDCLASS.
CLASS zcx_order_error IMPLEMENTATION.
METHOD constructor.
super->constructor( previous = previous ).
mv_order_id = iv_order_id.
mv_ship_date = iv_ship_date.
mv_quantity = iv_quantity.
mv_max_quantity = iv_max_quantity.
CLEAR me->textid.
IF textid IS INITIAL.
if_t100_message~t100key = order_not_found.
ELSE.
if_t100_message~t100key = textid.
ENDIF.
ENDMETHOD.
ENDCLASS.

3. Exception werfen (RAISE EXCEPTION)

CLASS lcl_order_service DEFINITION.
PUBLIC SECTION.
METHODS: get_order
IMPORTING iv_order_id TYPE string
RETURNING VALUE(rs_order) TYPE ty_order
RAISING zcx_order_error.
METHODS: ship_order
IMPORTING iv_order_id TYPE string
RAISING zcx_order_error.
ENDCLASS.
CLASS lcl_order_service IMPLEMENTATION.
METHOD get_order.
" Bestellung suchen
SELECT SINGLE * FROM ztorders
WHERE order_id = @iv_order_id
INTO @rs_order.
IF sy-subrc <> 0.
RAISE EXCEPTION TYPE zcx_order_error
EXPORTING
textid = zcx_order_error=>order_not_found
iv_order_id = iv_order_id.
ENDIF.
ENDMETHOD.
METHOD ship_order.
DATA: ls_order TYPE ty_order.
ls_order = get_order( iv_order_id ).
IF ls_order-status = 'SHIPPED'.
RAISE EXCEPTION TYPE zcx_order_error
EXPORTING
textid = zcx_order_error=>order_already_shipped
iv_order_id = iv_order_id
iv_ship_date = ls_order-ship_date.
ENDIF.
" Versand durchführen...
ENDMETHOD.
ENDCLASS.

4. Exception fangen und verarbeiten

DATA: lo_service TYPE REF TO lcl_order_service,
ls_order TYPE ty_order.
lo_service = NEW #( ).
TRY.
ls_order = lo_service->get_order( '12345' ).
CATCH zcx_order_error INTO DATA(lx_error).
" Fehlertext ausgeben
DATA(lv_message) = lx_error->get_text( ).
WRITE: / 'Fehler:', lv_message.
" Auf spezifische Attribute zugreifen
WRITE: / 'Order ID:', lx_error->mv_order_id.
" Fehler weiterwerfen
" RAISE EXCEPTION lx_error.
ENDTRY.

5. Exception-Kette (PREVIOUS)

METHOD process_order.
TRY.
" Datenbank-Operation
validate_order( is_order ).
CATCH zcx_validation_error INTO DATA(lx_val).
" Ursprüngliche Exception als PREVIOUS übergeben
RAISE EXCEPTION TYPE zcx_order_error
EXPORTING
textid = zcx_order_error=>order_not_found
previous = lx_val
iv_order_id = is_order-id.
ENDTRY.
ENDMETHOD.
" Beim Fangen: Kette durchlaufen
TRY.
process_order( ls_order ).
CATCH cx_root INTO DATA(lx_error).
" Alle Exceptions in der Kette ausgeben
DATA(lx_current) = lx_error.
WHILE lx_current IS BOUND.
WRITE: / lx_current->get_text( ).
lx_current = lx_current->previous.
ENDWHILE.
ENDTRY.

6. Exception ohne Nachrichtenklasse

CLASS zcx_simple_error DEFINITION
PUBLIC
INHERITING FROM cx_static_check
CREATE PUBLIC.
PUBLIC SECTION.
" Einfacher Text ohne T100
DATA: mv_message TYPE string READ-ONLY.
METHODS: constructor
IMPORTING
iv_message TYPE string OPTIONAL
previous LIKE previous OPTIONAL.
METHODS: if_message~get_text REDEFINITION.
ENDCLASS.
CLASS zcx_simple_error IMPLEMENTATION.
METHOD constructor.
super->constructor( previous = previous ).
mv_message = iv_message.
ENDMETHOD.
METHOD if_message~get_text.
result = mv_message.
ENDMETHOD.
ENDCLASS.
" Verwendung
RAISE EXCEPTION TYPE zcx_simple_error
EXPORTING iv_message = 'Ein Fehler ist aufgetreten'.

7. COND/SWITCH mit THROW

DATA: lv_age TYPE i VALUE -5.
" Exception in Ausdruck werfen
DATA(lv_status) = COND string(
WHEN lv_age >= 0 AND lv_age < 18 THEN 'Minderjährig'
WHEN lv_age >= 18 THEN 'Erwachsen'
ELSE THROW zcx_validation_error(
textid = zcx_validation_error=>invalid_input
iv_field = 'AGE'
)
).
" Mit SWITCH
DATA(lv_result) = SWITCH string( lv_status
WHEN 'A' THEN 'Aktiv'
WHEN 'I' THEN 'Inaktiv'
ELSE THROW zcx_validation_error( iv_field = 'STATUS' )
).

8. Lokale Exception-Klasse

" Für Tests oder lokale Verwendung
CLASS lcx_local_error DEFINITION
INHERITING FROM cx_static_check.
PUBLIC SECTION.
DATA: mv_info TYPE string READ-ONLY.
METHODS: constructor
IMPORTING iv_info TYPE string OPTIONAL
previous LIKE previous OPTIONAL.
METHODS: if_message~get_text REDEFINITION.
ENDCLASS.
CLASS lcx_local_error IMPLEMENTATION.
METHOD constructor.
super->constructor( previous = previous ).
mv_info = iv_info.
ENDMETHOD.
METHOD if_message~get_text.
result = |Lokaler Fehler: { mv_info }|.
ENDMETHOD.
ENDCLASS.

9. Exception-Hierarchie für Domain

" Basisklasse für alle Order-Exceptions
CLASS zcx_order_base DEFINITION
PUBLIC
ABSTRACT
INHERITING FROM cx_static_check
CREATE PUBLIC.
PUBLIC SECTION.
DATA: mv_order_id TYPE string READ-ONLY.
METHODS: constructor
IMPORTING iv_order_id TYPE string OPTIONAL
previous LIKE previous OPTIONAL.
ENDCLASS.
" Spezifische Exceptions
CLASS zcx_order_not_found DEFINITION
PUBLIC
INHERITING FROM zcx_order_base
CREATE PUBLIC.
" ...
ENDCLASS.
CLASS zcx_order_locked DEFINITION
PUBLIC
INHERITING FROM zcx_order_base
CREATE PUBLIC.
" ...
ENDCLASS.
CLASS zcx_order_invalid_state DEFINITION
PUBLIC
INHERITING FROM zcx_order_base
CREATE PUBLIC.
" ...
ENDCLASS.
" Fangen nach Hierarchie
TRY.
process_order( ).
CATCH zcx_order_not_found INTO DATA(lx_not_found).
" Spezifische Behandlung
CATCH zcx_order_locked INTO DATA(lx_locked).
" Spezifische Behandlung
CATCH zcx_order_base INTO DATA(lx_order).
" Alle anderen Order-Exceptions
CATCH cx_static_check INTO DATA(lx_other).
" Alle anderen statischen Exceptions
ENDTRY.

10. MESSAGE-Integration

" Nachrichtenklasse ZMSG mit Nachricht 001:
" "Feld & enthält ungültigen Wert"
CLASS zcx_with_message DEFINITION
PUBLIC
INHERITING FROM cx_static_check
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES: if_t100_message.
INTERFACES: if_t100_dyn_msg.
CONSTANTS:
BEGIN OF field_invalid,
msgid TYPE symsgid VALUE 'ZMSG',
msgno TYPE symsgno VALUE '001',
attr1 TYPE scx_attrname VALUE 'MV_FIELD',
attr2 TYPE scx_attrname VALUE '',
attr3 TYPE scx_attrname VALUE '',
attr4 TYPE scx_attrname VALUE '',
END OF field_invalid.
DATA: mv_field TYPE string READ-ONLY.
METHODS: constructor
IMPORTING textid LIKE if_t100_message=>t100key OPTIONAL
previous LIKE previous OPTIONAL
iv_field TYPE string OPTIONAL.
ENDCLASS.
" Exception werfen
RAISE EXCEPTION TYPE zcx_with_message
EXPORTING
textid = zcx_with_message=>field_invalid
iv_field = 'KUNNR'.
" Text abrufen
CATCH zcx_with_message INTO DATA(lx_error).
" get_text() liefert: "Feld KUNNR enthält ungültigen Wert"
DATA(lv_msg) = lx_error->get_text( ).
" Oder als MESSAGE
MESSAGE lx_error TYPE 'I'.

11. RESUMABLE Exceptions

CLASS zcx_resumable_error DEFINITION
PUBLIC
INHERITING FROM cx_static_check
CREATE PUBLIC.
PUBLIC SECTION.
DATA: mv_value TYPE i READ-ONLY.
METHODS: constructor
IMPORTING iv_value TYPE i OPTIONAL
previous LIKE previous OPTIONAL.
ENDCLASS.
METHOD process_values.
LOOP AT lt_values INTO DATA(lv_value).
IF lv_value < 0.
" RESUMABLE: Kann mit RESUME fortgesetzt werden
RAISE RESUMABLE EXCEPTION TYPE zcx_resumable_error
EXPORTING iv_value = lv_value.
ENDIF.
" Weiterverarbeitung...
ENDLOOP.
ENDMETHOD.
" Aufruf mit RESUME
TRY.
process_values( ).
CATCH BEFORE UNWIND zcx_resumable_error INTO DATA(lx_error).
WRITE: / 'Warnung: Negativer Wert', lx_error->mv_value.
RESUME. " Fortsetzen nach RAISE RESUMABLE
ENDTRY.

12. Best Practice: Exception Factory

CLASS zcx_factory DEFINITION.
PUBLIC SECTION.
CLASS-METHODS:
not_found
IMPORTING iv_entity TYPE string
iv_id TYPE string
RETURNING VALUE(ro_exception) TYPE REF TO zcx_not_found,
validation_failed
IMPORTING iv_field TYPE string
iv_message TYPE string
RETURNING VALUE(ro_exception) TYPE REF TO zcx_validation_error.
ENDCLASS.
CLASS zcx_factory IMPLEMENTATION.
METHOD not_found.
ro_exception = NEW #(
iv_entity = iv_entity
iv_id = iv_id
).
ENDMETHOD.
METHOD validation_failed.
ro_exception = NEW #(
iv_field = iv_field
iv_message = iv_message
).
ENDMETHOD.
ENDCLASS.
" Verwendung
RAISE EXCEPTION zcx_factory=>not_found(
iv_entity = 'Customer'
iv_id = '12345'
).

Exception-Klasse in SE24 erstellen

  1. SE24 → Klasse anlegen (Name beginnt mit ZCX_ oder YCX_)
  2. Superklasse: CX_STATIC_CHECK (oder andere)
  3. Interfaces: IF_T100_MESSAGE hinzufügen
  4. Attribute: Fehlerdaten definieren (z.B. MV_ORDER_ID)
  5. Texts: Exception-IDs mit Nachrichtenklasse verknüpfen
  6. Konstruktor: Parameter für Attribute definieren

Wichtige Hinweise / Best Practice

  • CX_STATIC_CHECK für Business-Exceptions (müssen in RAISING deklariert werden).
  • Eigene Hierarchie für fachliche Domänen (z.B. ZCX_ORDER_BASE).
  • IF_T100_MESSAGE für mehrsprachige Texte mit Nachrichtenklasse.
  • PREVIOUS für Exception-Ketten (Ursache nachverfolgen).
  • READ-ONLY für Exception-Attribute (Immutability).
  • Fangen Sie spezifische Exceptions zuerst, dann allgemeinere.
  • THROW in COND/SWITCH für Inline-Exceptions.
  • RESUMABLE für Warnungen, die übersprungen werden können.
  • Exception-Texte in Nachrichtenklasse für Übersetzbarkeit.
  • get_text() für formatierte Fehlermeldung.