This is a test message to test the length of the message box.
Login
ABAP OO Ausnahmeklasse
Erstellt von Software-Heroes

ABAP OO - Ausnahmeklassen

Heute schauen wir uns die verschiedenen Ausnahmeklassen an und wie du selbst eigene Ausnahmeklassen anlegen und verwenden kannst.

Werbung

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.

 

Quellen:
SAP Dokumentation - Ausnahmekategorien


Enthaltene Themen:
OOABAP OOAusnahmeklassen
Kommentare (0)

ABAP OO - Methodenschnittstellen

Kategorie - ABAP

Wie sollten Methodenschnittstellen aktuell aussehen und wie erreichst du diesen Zustand? In diesem Artikel werden wir die Frage klären.

19.11.2021

ABAP OO - Verkettung und Casting

Kategorie - ABAP

In diesem Artikel geht es um Verkettung von Methoden- und Objektaufrufen, weiterhin möchten wir dir den Hintergrund zum Casting erläutern.

20.08.2021

ABAP OO - Data Access Object (DAO)

Kategorie - ABAP

In diesem Artikel schauen wir uns einmal die DAOs an, was du mit ihnen machen kannst und wobei sie dich unterstützen.

23.07.2021

ABAP OO - Konstanteninterface

Kategorie - ABAP

Die Verwendung von Konstanten in der objektorientierten Programmierung gehört zum Standard für die meisten Klassen. Heute zeigen wir dir ein einfaches Beispiel für übergreifende Objekte.

16.07.2021

ABAP OO - Fehlerbehandlung

Kategorie - ABAP

Wie sieht es mit der Fehlerbehandlung in der objektorientierten Programmierung aus? Auf diese Frage wollen wir in diesem Artikel eingehen.

02.07.2021