ABAP OO - Ausnahmeklassen
Heute schauen wir uns die verschiedenen Ausnahmeklassen an und wie du selbst eigene Ausnahmeklassen anlegen und verwenden kannst.
Inhaltsverzeichnis
Wie wir dir bereits im letzten Artikel näher gebracht haben, werden Ausnahmeklassen genutzt um Fehlersituationen abbilden zu können und dabei zusätzliche Informationen in der Ausfrufhierarchie nach oben transportieren zu können. Heute schauen wir uns dazu einmal die Ausnahmeklassen etwas genauer an und was du damit alles machen kannst.
Allgemein
Eine Ausnahmeklasse sieht im Grunde wie eine normale Klasse aus, beginnt aber in den meisten Fällen mit CX_ oder ZCX_, um sich so von den Standardklasse abzuheben. Sie transportiert meist eine Nachricht, die den Fehler näher spezifiziert und hat erbt immer von einem der Grundtypen. Die oberste Ausnahmeklasse ist CX_ROOT, diese implementiert die Grundlagen einer Ausnahmeklasse, wird aber niemals zum Erstellen einer neuen Klasse verwendet. Hierbei handelt es sich um eine abstrakte Klasse, ebenso wie die folgenden drei Klassen, von denen wir auch erben können. Dazu einmal die ABAP Type Hierarchy aus Eclipse:
CX_STATIC_CHECK
Die Klasse wird am Häufigsten verwendet, wenn du eine Ausnahmeklasse anlegen willst. Bei dieser Klasse wird die Schnittstelle der Methode geprüft, ob die Ausnahme aufgeführt ist, wenn die Ausnahme innerhalb erzeugt und nicht mit einem CATCH abgefangen wird. Ist die Ausnahmedefinition nicht vorhanden, dann wird der Compiler darauf hinweisen.
Ist die Ausnahme nicht in der Methodenschnittstelle definiert, so kann diese nicht abgefangen werden, auch wenn du einen TRY/CATCH verwendest, der genau diese Meldung abfangen soll.
TRY.
raise_static_check( ).
CATCH zcx_bs_demo_static_check.
out->write( 'Catched STATIC_CHECK' ).
ENDTRY.
Hinweis: Die Ausnahmeklasse muss immer in der Schnittstellendefinition unter RAISING bekanntgegeben werden, ansonsten kann der Verwender nicht auf die Ausnahme reagieren.
CX_NO_CHECK
Wie der Name der Ausnahmeklasse vermuten lässt, wird hier keine Prüfung durchgeführt, die Ausnahme muss nicht in der Methodenschnittstelle definiert sein. Die Ausnahme ist sozusagen global bekannt und kann beim Auftreten abgefangen werden.
TRY.
raise_no_check( ).
CATCH zcx_bs_demo_no_check.
out->write( 'Catched NO_CHECK' ).
ENDTRY.
Hinweis: Die Verwendung dieser Ausnahmeklasse macht es dem Aufrufer schwer, sauber auf die Ausnahme zu reagieren, solange er sie nicht kennt. Solche Ausnahmen sind gut geeignet, Dumps zu dokumentieren. Die Klasse kann in der Methodenschnittstelle definiert werden, muss aber nicht.
CX_DYNAMIC_CHECK
Bei der Verwendung dieser Ausnahmeklasse wird die Definition in der Schnittstelle zur Entwicklungslaufzeit nicht geprüft, wird ein Typ dieser Ausnahme aber während der Verarbeitung ausgelöst, kann sie nur abgefangen werden, wenn sie in der Schnittstelle definiert wurde.
TRY.
raise_dynamic_check( ).
CATCH zcx_bs_demo_dynamic_check.
out->write( 'Catched DYNAMIC_CHECK' ).
CATCH cx_root.
out->write( 'Catched ROOT' ).
ENDTRY.
Beispiel
Dazu einmal das gesamte Beispiel an einer ausführbaren Klasse und wie die Methoden am Ende definiert sind, damit die Ausnahmen korrekt abgefangen werden können.
CLASS zcl_bs_demo_class_exception DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PROTECTED SECTION.
PRIVATE SECTION.
METHODS:
raise_static_check
RAISING
zcx_bs_demo_static_check,
raise_no_check,
raise_dynamic_check
RAISING
zcx_bs_demo_dynamic_check.
ENDCLASS.
CLASS zcl_bs_demo_class_exception IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
TRY.
raise_static_check( ).
CATCH zcx_bs_demo_static_check.
out->write( 'Catched STATIC_CHECK' ).
CATCH cx_root.
out->write( 'Catched ROOT' ).
ENDTRY.
TRY.
raise_no_check( ).
CATCH zcx_bs_demo_no_check.
out->write( 'Catched NO_CHECK' ).
CATCH cx_root.
out->write( 'Catched ROOT' ).
ENDTRY.
TRY.
raise_dynamic_check( ).
CATCH zcx_bs_demo_dynamic_check.
out->write( 'Catched DYNAMIC_CHECK' ).
CATCH cx_root.
out->write( 'Catched ROOT' ).
ENDTRY.
ENDMETHOD.
METHOD raise_static_check.
RAISE EXCEPTION NEW zcx_bs_demo_static_check( ).
ENDMETHOD.
METHOD raise_no_check.
RAISE EXCEPTION NEW zcx_bs_demo_no_check( ).
ENDMETHOD.
METHOD raise_dynamic_check.
RAISE EXCEPTION NEW zcx_bs_demo_dynamic_check( ).
ENDMETHOD.
ENDCLASS.
CX_ROOT
Im letzten Beispiel hatten wir bereits die Ausnahmeklasse CX_ROOT verwendet, um weitere Fehler abfangen zu können, bevor es zu einem Dump kommt. Was steck also hinter der abstrakten Wurzelklasse für alle Ausnahmeklassen? Man sollte als erstes einmal keine Ausnahmeklasse auf Basis dieser Klasse aufbauen, sondern die drei gezeigten Unterklassen verwenden, um entsprechende Verwendungszwecke zu erben. Man kann die Klasse aber an Stellen verwenden, an der man jegliche Verarbeitungsfehler abfangen möchte.
In den oben genannten Beispielen wurde CX_ROOT immer dann gefangen, wenn die Methodenschnittstelle nicht richtig konfiguriert wurde. Wenn zum Beispiel in der Definition der Methode RAISE_STATIC_CHECK der RAISING Zusatz nicht definiert wurde, kann die auftretende Ausnahme nur mit CX_ROOT abgefangen werden und nicht mehr mit der eigentlichen Ausnahmeklasse.
Eigene Ausnahmeklasse
Bei der Definition einer eigenen Ausnahmeklasse bist du nicht unbedingt auf globale Ausnahmeklassen angewiesen, sondern kannst deine Ausnahmeklassen auch lokal definieren in Reports oder auch globalen Klassen. In unserem Beispiel bauen wir dabei auf eine globale Ausnahmeklasse die wir von STATIC_CHECK ableiten.
Unsere Klasse definieren wir wie folgt:
CLASS zcx_bs_demo_data_error DEFINITION PUBLIC
INHERITING FROM cx_static_check
FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_t100_dyn_msg.
INTERFACES if_t100_message.
CONSTANTS:
BEGIN OF cs_data_error,
msgid TYPE symsgid VALUE 'ZBS_DEMO_LOG',
msgno TYPE symsgno VALUE '003',
attr1 TYPE scx_attrname VALUE '',
attr2 TYPE scx_attrname VALUE '',
attr3 TYPE scx_attrname VALUE '',
attr4 TYPE scx_attrname VALUE '',
END OF cs_data_error,
BEGIN OF cs_type_error,
msgid TYPE symsgid VALUE 'ZBS_DEMO_LOG',
msgno TYPE symsgno VALUE '004',
attr1 TYPE scx_attrname VALUE '',
attr2 TYPE scx_attrname VALUE '',
attr3 TYPE scx_attrname VALUE '',
attr4 TYPE scx_attrname VALUE '',
END OF cs_type_error.
DATA:
md_error TYPE sysubrc.
METHODS constructor
IMPORTING
!textid LIKE if_t100_message=>t100key OPTIONAL
!previous LIKE previous OPTIONAL
id_error TYPE sysubrc.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcx_bs_demo_data_error IMPLEMENTATION.
METHOD constructor ##ADT_SUPPRESS_GENERATION.
CALL METHOD super->constructor
EXPORTING
previous = previous.
CLEAR me->textid.
IF textid IS INITIAL.
if_t100_message~t100key = cs_data_error.
ELSE.
if_t100_message~t100key = textid.
ENDIF.
md_error = id_error.
ENDMETHOD.
ENDCLASS.
Was haben wir also für unsere Klasse angepasst?
- Die Klasse enthält nun zwei unterschiedliche Fehlermeldungen, die in zwei Konstantenstrukturen abgebildet sind. Die Fehlermeldung CS_DATA_ERROR wird standardmäßig in der Klasse gesetzt, wenn nicht eine andere Fehlermeldung übergeben wird.
- Zusätzlich gibt es noch ein neues Attribut mit dem wir weitere Informationen transportieren können, dieses Attribut könnte auch auf READ-ONLY gestellt werden.
- Erweiterung des Konstruktor um einen IMPORTING Parameter, um das Attribut zu versorgen.
Erzeugen wir über diese Klasse zwei verschieden Fehler, beim Ersten übergeben wir nur den zusätzlichen Parameter, beim Zweiten ändern wir auch die Meldung, die die Klasse ausgibt, indem wir die andere definierte Meldung übergeben.
" Error with additional parameter
RAISE EXCEPTION NEW zcx_bs_demo_data_error(
id_error = 4
).
" Error with different message
RAISE EXCEPTION NEW zcx_bs_demo_data_error(
id_error = 3
textid = zcx_bs_demo_data_error=>cs_type_error
).
Wenn auf den Fehler reagiert wird, kannst du nun auf das entsprechende Attribut zugreifen, um dieses zum Beispiel weiter zu verwenden oder darauf zu referenzieren. Dabei muss der erzeugte Typ natürlich von deiner definierten Klasse sein. Weiterhin stehen dir die Methoden GET_TEXT und GET_LONGTEXT zur Verfügung, damit du die Meldungen in Texte (String) umwandeln und zurückgeben kannst.
TRY.
CATCH zcx_bs_demo_data_error INTO DATA(lo_error).
out->write( lo_error->md_error ).
out->write( lo_error->get_text( ) ).
out->write( lo_error->get_longtext( ) ).
ENDTRY.
Das vollständige Beispiel sieht nun wie folgt aus, auch hier haben wir eine ausführbare Klasse zur einfacheren Verwendung eingesetzt:
CLASS zcl_bs_demo_own_class_exc DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PROTECTED SECTION.
PRIVATE SECTION.
METHODS:
raise_default_message
RAISING
zcx_bs_demo_data_error,
raise_changed_message
RAISING
zcx_bs_demo_data_error.
ENDCLASS.
CLASS zcl_bs_demo_own_class_exc IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
TRY.
raise_default_message( ).
raise_changed_message( ).
CATCH zcx_bs_demo_data_error INTO DATA(lo_error).
out->write( lo_error->md_error ).
out->write( lo_error->get_text( ) ).
out->write( lo_error->get_longtext( ) ).
ENDTRY.
ENDMETHOD.
METHOD raise_default_message.
RAISE EXCEPTION NEW zcx_bs_demo_data_error(
id_error = 4
).
ENDMETHOD.
METHOD raise_changed_message.
RAISE EXCEPTION NEW zcx_bs_demo_data_error(
id_error = 3
textid = zcx_bs_demo_data_error=>cs_type_error
).
ENDMETHOD.
ENDCLASS.
Previous
Dir ist sicherlich schon aufgefallen, dass der Konstruktor der Ausnahmeklasse auch einen Previous Parameter hat, dieser wird verwendet um Ausnahmen im Aufrufstack nach oben zu geben und so auch auf den Ursprungsfehler oder die Ursprungsmeldung zu kommen. Dazu verwenden wir einmal die bisher angelegten Ausnahmeklassen und erzeugen einen Fehler auf unterster Ebene. Auf eine der oberen Ebenen fangen wir nun den Fehler ab und verpacken diesen in eine neue Ausnahme, so können auch mehrere verschiedene Fehler in eine Ausnahmeklasse übernommen werden.
TRY.
raise_data_exception( ).
CATCH zcx_bs_demo_data_error INTO DATA(lo_error).
RAISE EXCEPTION NEW zcx_bs_demo_static_check( previous = lo_error ).
ENDTRY.
Wir befüllen den PREVIOUS Parameter mit der aktuell abgefangenen Ausnahme und erzeugen unsere eigene Ausnahme, um den Fehler weiterzugeben. Am Ende können wir nach dem Fangen der Ausnahme über das Attribut loopen bis wir die letzte Fehlermeldung im Stack haben, unsere eigene Ursprungsausnahme. So eine Schleife könnte daher wie folgt aussehen:
TRY.
CATCH zcx_bs_demo_static_check INTO DATA(lo_error).
DATA(lo_previous) = lo_error->previous.
DO.
IF lo_previous->previous IS NOT BOUND.
EXIT.
ENDIF.
lo_previous = lo_previous->previous.
ENDDO.
out->write( lo_previous->get_text( ) ).
ENDTRY.
Die Schleife wird verlassen, wenn PREVIOUS nicht mehr befüllt ist, dann gibt es keinen Vorgänger. Zum Abschluss noch das gesamte Beispiel das wir verwendet haben:
CLASS zcl_bs_demo_exc_hierarchy DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PROTECTED SECTION.
PRIVATE SECTION.
METHODS:
raise_static_exception
RAISING
zcx_bs_demo_static_check,
raise_data_exception
RAISING
zcx_bs_demo_data_error.
ENDCLASS.
CLASS zcl_bs_demo_exc_hierarchy IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
TRY.
raise_static_exception( ).
CATCH zcx_bs_demo_static_check INTO DATA(lo_error).
DATA(lo_previous) = lo_error->previous.
DO.
IF lo_previous->previous IS NOT BOUND.
EXIT.
ENDIF.
lo_previous = lo_previous->previous.
ENDDO.
out->write( lo_previous->get_text( ) ).
ENDTRY.
ENDMETHOD.
METHOD raise_static_exception.
TRY.
raise_data_exception( ).
CATCH zcx_bs_demo_data_error INTO DATA(lo_error).
RAISE EXCEPTION NEW zcx_bs_demo_static_check( previous = lo_error ).
ENDTRY.
ENDMETHOD.
METHOD raise_data_exception.
RAISE EXCEPTION NEW zcx_bs_demo_data_error( id_error = 8 ).
ENDMETHOD.
ENDCLASS.
Fazit
Das Verwenden von Ausnahmeklassen ist recht einfach, doch die Meisterung dieser nicht ganz so leicht. Eigene Ausnahmeklassen definieren und mit eigenen Attributen und Informationen auszustatten, kann zu einer Herausforderung werden.