Hierarchie von Ausnahmen anzeigen

In einem Projekt stand ich erneut vor der Aufgabe, eine Hierarchie von ausgelösten Exceptions anzeigen zu müssen, um einem Fehler auf die Spur zu kommen. Natürlich kann man sich im Debugger das Ausnahmeobjekt anzeigen lassen und jeweils die PREVIOUS-Ausnahmeobjekte durchklicken. Allerdings funktioniert das nur, wenn man selbst den Fehler nachstellen kann. Einfacher wäre es unter Umständen, wenn der Anwender bereits die Aufrufhierarchie sehen könnte. Das könnten zwar durchaus zu viele und vor allen Dingen zu technische Informationen sein, aber in jedem Fall besser, wenn man dem Entwickler diesen Screenshot schicken kann, als nur die lapidare Meldung “Es ist eine Ausnahme aufgetreten”.

Da es im Standard anscheinend keinen Baustein gibt, um diese Aufrufhierarchie anzuzeigen – jedenfalls habe ich keinen gefunden –, habe ich kurzerhand selbst eine kleine Funktion geschrieben.

Worum geht es genau?

Klassenbasierte Ausnahmen können nach oben, also an den Aufrufer, propagiert werden. Dabei kann der Ausnahme immer der Parameter PREVIOUS übergeben werden. In diesem Parameter kann ein Ausnahmeobjekt eines vorherigen Fehlers übergeben werden. Das bietet die Möglichkeit, die komplette Fehlerhistorie nachvollziehen zu können.

Beispielprogramm

Das folgende Beispielprogramm soll das Vorgehen kurz und knapp demonstrieren. Ich habe hier lokal drei Ausnahmeklassen definiert:

  • ERR_ST – erbt von CX_STATIC_CHECK und hat ein IF_T100-Interface
  • ERR_DYN – erbt von CX_DYNAMIC_CHECK und hat ein IF_T100-Interface
  • ERR_PURE – erbt von CX_STATIC_CHECK und hat kein Interface für MESSAGES

Die Klasse DEMO besteht aus den vier Methoden ONE, TWO, THREE und GO. GO ist die Startmethode und ruft ONE auf, die TWO aufruft, die THREE aufruft. Jede Methode benutzt dabei eine andere Ausnahmeklassen. Die vorher ausgelöste Ausnahme wird als PREVIOUS übergeben. Am Ende hat man eine Hierarchie von drei Ausnahmeinstanzen.

Code

REPORT. 
CLASS err_st DEFINITION INHERITING FROM cx_static_check CREATE PUBLIC.
  PUBLIC SECTION.
    INTERFACES if_t100_message.
    INTERFACES if_t100_dyn_msg.
ENDCLASS.
CLASS err_dyn DEFINITION INHERITING FROM cx_dynamic_check.
  PUBLIC SECTION.
    INTERFACES if_t100_message.
    INTERFACES if_t100_dyn_msg.
ENDCLASS.

CLASS err_pure DEFINITION INHERITING FROM cx_static_check.
  PUBLIC SECTION.
    DATA text TYPE string.
    METHODS constructor
      IMPORTING
        text TYPE clike OPTIONAL.
    methods get_text REDEFINITION.
ENDCLASS.

CLASS err_pure IMPLEMENTATION.
  METHOD constructor.
    super->constructor( ).
    me->text = text.
  ENDMETHOD.
  method get_text.
    result = me->text.
  ENDMETHOD.
ENDCLASS.

CLASS demo DEFINITION.
  PUBLIC SECTION.
    METHODS one RAISING err_st.
    METHODS two.
    METHODS three RAISING err_pure.
    METHODS go.
ENDCLASS.

CLASS demo IMPLEMENTATION.
  METHOD one.
    TRY.
        two( ).
      CATCH err_dyn INTO DATA(err).
        RAISE EXCEPTION TYPE err_st
          MESSAGE ID 'OO'
          TYPE 'E'
          NUMBER '000'
          WITH 'Method one failed'
          EXPORTING
            previous = err.
    ENDTRY.
  ENDMETHOD.
  METHOD two.
    TRY.
        three( ).
      CATCH cx_root INTO DATA(err).
        RAISE EXCEPTION TYPE err_dyn
          MESSAGE ID 'OO'
          TYPE 'E'
          NUMBER '000'
          WITH 'Failure method two'
          EXPORTING
            previous = err.
    ENDTRY.
  ENDMETHOD.
  METHOD three.
    data(err) = new err_pure( text = 'pure exception w/o T100 interface' ).
    RAISE EXCEPTION err.

  ENDMETHOD.
  METHOD go.
    TRY.
        one( ).
      CATCH cx_root INTO DATA(error).
        excp=>show( error ).
    ENDTRY.
  ENDMETHOD.
ENDCLASS.

START-OF-SELECTION.
  NEW demo( )->go( ).

Anzeige der Ausnahmen

Im oben gezeigten Coding wird die Methode EXCP=>SHOW aufgerufen. Diese stelle ich dir jetzt vor. Sie nimmt ein beliebiges Ausnahmeobjekt entgegen (vom Typ CX_ROOT oder Unterklassen) und sammelt Informationen über die Ausnahmehierarchie.

Mit Hilfe der Klasse CL_ABAP_CLASSDESCR wird der Name der Ausnahmeklasse ermittelt. Klasse CL_MESSAGE_HELPER hilft dabei, die MESSAGE-Variablen in die entsprechenden SY-Felder zu stellen. Die Ermittelten Daten werden mit Hilfe des CL_SALV_TABLE im Popup angezeigt.

Verbesserungen

Das hier vorgestellte Popup sollte einem Endanwender nicht direkt angezeigt werden, denn es werden sehr viele technische Details gezeigt. Sinnvoller wäre sicherlich eine Popup, dass eine entsprechende Nachricht oder die Meldung der letzten Ausnahme mit der Möglichkeit, die Aufrufhierarchie anzeigen zu lassen.

Meiner Meinung nach müsste diese Funktion bereits direkt in der SAPGUI beim Anzeigen einer Ausnahme-MESSAGE möglich sein, wenn die Meldung mittels MESSAGE exception_object TYPE ‘I’ ausgegeben wird.

Code

CLASS excp DEFINITION.
  PUBLIC SECTION.
    CLASS-METHODS show IMPORTING obj TYPE REF TO cx_root.
  PRIVATE SECTION.
    TYPES: BEGIN OF _exception,
             message       TYPE string,
             msgno         TYPE symsgno,
             msgid         TYPE symsgid,
             msgv1         TYPE symsgv,
             msgv2         TYPE symsgv,
             msgv3         TYPE symsgv,
             msgv4         TYPE symsgv,
             relative_name TYPE string,
             absolute_name TYPE string,
             prog_name     TYPE syrepid,
             incl_name     TYPE inclname,
             source_line   TYPE i,
           END OF _exception,
           _exceptions TYPE STANDARD TABLE OF _exception WITH DEFAULT KEY.
    CLASS-DATA exceptions TYPE _exceptions.
    CLASS-METHODS fill_table IMPORTING obj TYPE REF TO cx_root.
    CLASS-METHODS display_popup.
ENDCLASS.

CLASS excp IMPLEMENTATION.
  METHOD show.

    fill_table( obj ).
    display_popup( ).

  ENDMETHOD.

  METHOD fill_table.

    DATA(error) = CAST cx_root( obj ).
    WHILE error IS BOUND.
      APPEND INITIAL LINE TO exceptions ASSIGNING FIELD-SYMBOL(<excp>).

      DATA(descr) = cl_abap_classdescr=>describe_by_object_ref( error ).

      <excp>-absolute_name = descr->absolute_name.
      <excp>-relative_name = descr->get_relative_name( ).
      <excp>-message = error->get_text( ).
      error->get_source_position(
        IMPORTING
          program_name = <excp>-prog_name
          include_name = <excp>-incl_name
          source_line  = <excp>-source_line   ).

      IF error IS INSTANCE OF if_t100_message.
        DATA(msg) = CAST if_t100_message( error ).
        cl_message_helper=>set_msg_vars_for_if_t100_msg( msg ).
        <excp>-msgno = sy-msgno.
        <excp>-msgid = sy-msgid.
        <excp>-msgv1 = sy-msgv1.
        <excp>-msgv2 = sy-msgv2.
        <excp>-msgv3 = sy-msgv3.
        <excp>-msgv4 = sy-msgv4.
      ENDIF.
      error ?= error->previous.
    ENDWHILE.
  ENDMETHOD.
  METHOD display_popup.
    TRY.
        cl_salv_table=>factory(
          IMPORTING
            r_salv_table = DATA(popup)
          CHANGING
            t_table      = exceptions ).
        popup->set_screen_popup(
          start_column = 10
          end_column  = 170
          start_line  = 2
          end_line    = 10 ).
        popup->get_columns( )->set_optimize( abap_true ).
        popup->get_columns( )->get_column( 'MESSAGE' )->set_medium_text( `Message` ) ##no_text.
        popup->get_columns( )->get_column( 'ABSOLUTE_NAME' )->set_medium_text( `Absolute class name` ) ##no_text.
        popup->get_columns( )->get_column( 'RELATIVE_NAME' )->set_medium_text( `Relative class name` ) ##no_text.
        popup->display( ).

      CATCH cx_salv_msg cx_salv_not_found.
    ENDTRY.

  ENDMETHOD.
ENDCLASS.
Enno Wulff