Anfang des Inhaltsbereichs

Ausnahmen propagieren Dokument im Navigationsbaum lokalisieren

Klassenbasierte Ausnahmen, die in Prozeduren (Methoden, Funktionsbausteine, Unterprogramme) auftreten, müssen nicht unbedingt auch dort behandelt werden, sondern können an den Aufrufer der Prozedur propagiert werden. Wenn der Aufrufer wieder eine Prozedur ist und der Aufruf in einem TRY-Block stattfindet, kann der die Ausnahme behandeln oder selber an seinen Aufrufer propagieren und so fort.

Die höchste Ebene, in die Ausnahmen propagiert werden können, sind Verarbeitungsblöcke ohne lokalen Datenbereich, also Ereignisblöcke oder Dialogmodule. Hier müssen die aus aufgerufenen Prozeduren weitergereichten Ausnahmen sowie die im eigenen Coding aufgetretenen Ausnahmen behandelt werden, oder es kommt zu einem Laufzeitfehler.

Ein statischer Konstruktor einer Klasse kann prinzipiell keine Ausnahmen propagieren, da ein Verwender einer Klasse in der Regel nicht weiß, ob er der erste Verwender ist und deshalb eventuell vom statischen Konstruktor propagierte Azusnahmen behandeln muß oder nicht.

Ereignisbehandlermethoden können ebenfalls keine Ausnahmen propagieren, da dies gegen das Ereigniskonzept verstoßen würde: Der Behandler eines Ereignisses wird indirekt durch die Laufzeitumgebung aufgerufen, wenn ein Ereignis stattfindet. Er ist vom Auslöser des Ereignisses vollständig entkoppelt und kann ihm daher keine Ausnahmen weiterreichen. Umgekehrt kann der Auslöser eines Ereignisses nie wissen, welche Behandler auf das Ereignis reagieren, und deshalb nicht deren Ausnahmen behandeln.

Da Ausnahmen durch transiente Objekte repräsentiert werden, die nur im internen Modus eines Programms leben, können Ausnahmen nicht aus mit SUBMIT ... AND RETURN oder CALL TRANSACTION aufgerufenen Programmen an die Aufrufer propagiert werden.

Der RAISING-Zusatz

Um Ausnahmen aus Prozeduren zu propagieren, muß in der Regel bei der Definition der Schnittstelle einer Prozedur (außer bei statischen Konstruktoren und Ereignisbehandlern) der RAISING-Zusatz verwendet werden. Dadurch weiß der Aufrufer einer Prozedur, welche Ausnahmen in einer Prozedur auftreten können, kann sie behandeln, oder selbst durch RAISING weiterreichen.

In der RAISING-Klausel müssen die Klassen aller Ausnahmeklassen aufgezählt werden, deren Ausnahmen in der Prozedur auftreten können (Streng genommen gilt dies nur für deklarationspflichtige Ausnahmen. Mehr dazu weiter unten). Es können dabei auch Oberklassen angegeben werden, um Gruppen von Ausnahmen zusammenzufassen. Die Angabe der allgemeinsten Oberklasse CX_ROOT und ihrer direkten Unterklasse CX_NO_CHECK (siehe unten) ist aber nicht möglich. Da bei der Ausnahmebehandlung die Reihenfolge der CATCH-Blöcke durch die Vererbungshierarchie der Ausnahmeklassen festgelegt ist, müssen auch Ausnahmeklassen in der RAISING-Klausel in dieser Reihenfolge angegeben sein. Dies ermöglicht es dem Aufrufer einer Prozedur die CATCH-Blöcke nur durch Kenntnis der Prozedurschnittstelle in der richtigen Reihenfolge aufzuführen, ohne sich die Ausnahmeklassen im Class Builder ansehen zu müssen.

Für die möglichen Prozeduren sieht das wie folgt aus:

Methoden lokaler Klassen

Der RAISING-Zusatz wird bei der Deklaration einer Methode angegeben:

METHODS meth ... RAISING cx_... cx_...

Hinter RAISING stehen die Ausnahmeklassen, deren Objekte propagiert werden sollen, in der oben beschriebenen Reihenfolge. Die gleichzeitige Angabe klassischer Ausnahmen mit dem Zusatz EXCEPTIONS ist nicht möglich.

Methoden globaler Klassen

Bei den Methoden globaler Klassen kann der RAISING Zusatz nicht direkt angegeben werden. Statt dessen müssen die Ausnahmeklassen, deren Objekte propagiert werden sollen in der Ausnahmetabelle der Methode im Class Builder eingetragen werden. Der Class Builder sortiert die angegebenen Ausnahmen in der oben beschriebenen Reihenfolge und generiert die zugehörige RAISING-Klausel.

Auf der Ausnahmetabelle des Class Builder muß hierfür das Ankreuzfeld Ausnahmeklassen angekreuzt sein. Ansonsten betrachtet der Class Builder die eingetragenen Ausnahmen als klassische Ausnahmen und generiert eine entsprechende EXCEPTIONS-Klausel! Die gleichzeitige Verwendung von Ausnahmeklassen und klassischer Ausnahmen ist also durch das Werkzeug ausgeschlossen.

Falls in der Ausnahmetabelle die Angabe einer deklarationspflichtigen Ausnahme (siehe unten) fehlt, meldet die Syntaxüberprüfung einen Fehler, da die RAISING-Klausel unvollständig ist.

Funktionsbausteine

Genau wie bei globalen Methoden im Class Builder werden Ausnahmen bei Funktionsbausteinen durch ihre Angabe im Function Builder propagiert. Der Function Builder verfügt hierfür über eine entsprechende Registerkarte, die genauso funktioniert, wie die Ausnahmetabelle einer Methode im Class Builder. Auch hier muß für das Propagieren von klassenbasierten Ausnahmen das entsprechende Ankreuzfeld markiert sein, da ansonsten klassische Ausnahmen deklariert werden.

Unterprogramme

Bei Unterprogrammen wird der RAISING-Zusatz bei der Definition desUnterprogramms angegeben:

FORM form ... RAISING cx_... cx_...

Hinter RAISING stehen die Ausnahmeklassen, deren Objekte propagiert werden sollen. Klassenbasierte Ausnahmen sind die einzigen Ausnahmen, die in der Schnittstelle eines Unterprogramms aufgeführt werden können.

Syntaktische Überprüfung

In der Regel sollen Ausnahmen, die in einer Prozedur auftreten, entweder dort behandelt oder mit dem Zusatz RAISING über die Schnittstelle der Prozedur nach oben weitergereicht werden. Dies wird von der Syntaxüberprüfung (bei Methoden) oder der erweiterten Programmprüfung (bei Funktionsbausteinen oder Unterprogrammen) unterstützt. Beispielsweise ist folgendes Unterprogramm syntaktisch falsch:

form TEST.
  raise exception type CX_DEMO_CONSTRUCTOR.
endform.

Der Fehler kann entweder dadurch behoben werden, daß die Ausnahme im Unterprogramm behandelt wird:

form TEST.
  try.
      raise exception type CX_DEMO_CONSTRUCTOR.
    catch CX_DEMO_CONSTRUCTOR.
      ...
  endtry.
endform.

Oder daß die Ausnahme über die RAISING-Klausel propagiert wird:

form TEST raising CX_DEMO_CONSTRUCTOR.
  raise exception type CX_DEMO_CONSTRUCTOR.
endform.

Obwohl diese syntaktische Überprüfung und der daraus folgende Zwang, Ausnahmen zu behandeln oder durch eine Deklaration in der Schnittstelle weiterzuleiten für die meisten anwendungsspezifischen Fehlersituationen gerechtfertigt ist, kann es auch Ausnahmesituationen geben, für die das nicht zutreffend ist, nämlich:

Die Behandlung oder das explizite Weiterreichen solcher Ausnahmen zu erzwingen ist nicht sinnvoll, da der Verwender durch Programmlogik sicherstellen kann, daß die Ausnahme nicht auftritt.

Eine konsequente Verfolgung des Behandlungs- bzw. Deklarationszwangs würde dazu führen, daß diese Ausnahmen in fast jeder Schnittstelle angegeben werden müßten. Dies würde nicht zu robusteren Programmen führen, sondern diese nur unleserlicher machen.

Um diesem Problem zu begegnen, wurden drei verschiedene Arten von Ausnahmeklassen eingeführt, nämlich für

  1. Deklarationspflichtige Ausnahmen, die prinzipiell entweder behandelt oder propagiert werden müssen. Das ist der bisher beschriebene Regelfall.
  2. Ausnahmen, die in der Schnittstelle deklariert werden können, aber nicht müssen.
  3. Ausnahmen, die nicht in der Schnittstelle deklariert werden dürfen.

Dies wird durch drei direkte Unterklassen der Oberklasse CX_ROOT verwirklicht:

  1. CX_STATIC_CHECK für Ausnahmen, die deklariert werden müssen.
  2. CX_DYNAMIC_CHECK für Ausnahmen, die nicht deklariert werden müssen.
  3. CX_NO_CHECK für Ausnahmen, die nicht deklariert werden dürfen.

Diese drei Klassen sind ebenso wie ihre Oberklasse CX_ROOT abstrakt und alle anderen Ausnahmeklassen sind Unterklassen dieser drei Klassen. Je nach Oberklasse gehört jede Ausnahmeklasse zu einer der drei Arten.

Ausnahmen, die deklariert werden müssen

Die zugehörigen Ausnahmeklassen sind Unterklassen von CX_STATIC_CHECK. Für sie gilt das allgemeine Prinzip, daß eine entsprechende Ausnahme entweder behandelt oder explizit mit dem RAISING-Zusatz weitergereicht werden muß, was beim Syntaxcheck überprüft wird. Wenn die Meldungen der Syntaxprüfung bzw. erweiterten Programmprüfung nicht ignoriert werden, können deklarationspflichtige Ausnahmen nur dann zu Laufzeitfehlern führen, wenn sie auch in der obersten Aufrufebene nicht behandelt werden.

Zur Zeit sind nur selbstdefinierte Ausnahmen für Fehlersituationen im Anwendungscoding Unterklassen von CX_STATIC_CHECK. Es gibt keine vordefinierten Ausnahmen CX_SY_... für Fehlersituationen in der Laufzeitumgebung, die Unterklassen von CX_STATIC_CHECK sind.

Ausnahmen, die nicht deklariert werden müssen

Die zugehörigen Ausnahmeklassen sind Unterklassen von CX_DYNAMIC_CHECK. Falls eine solche Ausnahme zur Laufzeit auftritt, muß sie genau wie bei Unterklassen von CX_STATIC_CHECK entweder behandelt oder explizit mit dem RAISING-Zusatz weitergereicht werden. Dies wird aber nicht beim Syntaxcheck überprüft. Wird eine solche Ausnahme zur Laufzeit weder behandelt, noch weitergereicht, kommt es zu einem Laufzeitfehler.

Solche Ausnahmeklassen sind sinnvoll für potentielle Fehlersituationen, die aber nicht behandelt oder weitergereicht werden müssen, da die Programmlogik sie meist ausschließen kann. Die meisten der vordefinierten Ausnahmen CX_SY_... für Fehlersituationen in der Laufzeitumgebung sind Unterklassen von CX_DYNAMIC_CHECK. Ein typisches Beispiel hierfür ist die Klasse CX_SY_ZERODIVIDE. Der Verwender einer Division kann diese Ausnahme vermeiden, indem er sicherstellt, daß der Divisor nicht 0 ist. Eine Deklarationspflicht für vordefinierte Ausnahmeklassen der Laufzeitumgebung wäre andererseits auch gar nicht möglich, da ansonsten jede potentielle Ausnahme jeder ABAP-Anweisung einer Prozedur behandelt oder propagiert werden müsste.

Da Ausnahmen der Kategorie CX_DYNAMIC_CHECK nicht in der Schnittstelle deklariert werden müssen, ist ihre Verhinderung oder Behandlung Sache der aufgerufenen Prozedur. Der Aufrufer darf nicht damit rechnen müssen, daß eine solche Ausnahme weitergereicht wird.

Ausnahmen, die nicht deklariert werden dürfen

Die zugehörigen Ausnahmeklassen sind Unterklassen von CX_NO_CHECK. Solche Ausnahmen können behandelt werden. Ansonsten werden sie automatisch weitergereicht. Die RAISING-Klausel enthält implizit den Zusatz CX_NO_CHECK. Diese Klasse und ihre Unterklassen brauchen und dürfen nicht angegeben werden. Der Syntaxcheck findet hier also nie einen Fehler. Alle Ausnahmen der Kategorie CX_NO_CHECK, die nicht in der Aufrufhierarchie behandelt werden, gelangen automatisch in die höchste Aufrufebene und führen zu einem Laufzeitfehler, wenn sie auch dort nicht abgefangen werden.

Einige der vordefinierten Ausnahmen CX_SY_... für Fehlersituationen in der Laufzeitumgebung sind Unterklassen von CX_NO_CHECK. Ein Beispiel hierfür ist die Klasse CX_SY_EXPORT_TO_BUFFER_NO_MEMORY, die für einen bestimmten Ressourcenengpaß zuständig ist, aber in der Regel nicht an Ort und Stelle behandelt werden kann. Der Aufrufer einer Prozedur muß prinzipiell immer damit rechnen, daß die Prozedur Ausnahmen der Kategorie CX_NO_CHECK propagieren kann.

Zusammenfassung zur Deklaration von Ausnahmeklassen

In der RAISING-Klausel der Schnittstelle einer Prozedur müssen syntaktisch nur die Ausnahmen deklariert werden, die auch tatsächlich die Schnittstelle passieren können. Ausnahmen, die bereits in der Prozedur behandelt werden, sind für die Schnittstelle nicht relevant. Die Menge aller Ausnahmen, die in der Schnittstelle deklariert werden müssen, läßt sich wie folgt bestimmen:

Diese Regeln sind rekursiv anzuwenden, wenn TRY-Konstrukte innerhalb von TRY- oder CATCH-Blöcken vorkommen.

Verletzung der Schnittstelle

Unter einer Verletzung der Schnittstelle verstehen wir den Fall, daß eine in einer Prozedur unbehandelte Ausnahme, nicht mit RAISING weitergereicht wird. Nach obigen Erklärungen kann dieser Fall nur für Ausnahmen der Kategorien CX_DYNAMIC_CHECK oder CX_STATIC_CHECK auftreten, da Ausnahmen der Kategorie CX_NO_CHECK immer implizit propagiert werden. Für Ausnahmen der Kategorie CX_STATIC_CHECK kann der Fall auch nur bei Ignorierung der Syntaxfehlermeldung auftreten.

Wird die Schnittstelle solcherart verletzt, wird das Programm nicht mit einem Laufzeitfehler beendet, sondern es wird eine neue Ausnahme der vordefinierten Klasse CX_SY_NO_HANDLER, die von CX_NO_CHECK erbt, ausgelöst und eine Referenz auf die ursprüngliche Ausnahme in ihrem Attribut PREVIOUS hinterlegt.

Das Auftreten einer Ausnahme CX_SY_NO_HANDLER bedeutet im Regelfall, daß der Entwickler einer Prozedur vergessen hat, eine Ausnahme der Kategorie CX_DYNAMIC_CHECK lokal zu behandeln oder zu verhindern und nicht, daß er vergessen hat, die RAISING-Klausel entsprechend zu erweitern. Ein Behandler für eine solche Ausnahme kann nicht die ursprüngliche Ausnahme abfangen, sondern nur auf einen Programmierfehler in der aufgerufenden Prozedur hinweisen.

Da die Schnittstellen von Ereignisbehandlern und statischen Konstruktoren keine expliziten RAISING-Klauseln enthalten können, führen nicht behandelte Ausnahmen der Kategorien CX_DYNAMIC_CHECK oder CX_STATIC_CHECK prinzipiell zur Ausnahme CX_SY_NO_HANDLER, die im Programm behandelt werden kann.

Beispiel

Beispiel

report  DEMO_PROPAGATE_EXCEPTIONS.

class A_CLASS definition.
  public section.
    methods FOO importing
                   P type STRING
                 raising
                   CX_DEMO_CONSTRUCTOR
                   CX_DEMO_ABS_TOO_LARGE.
endclass.

class B_CLASS definition.
  public section.
    data A type ref to A_CLASS.
    methods BAR raising CX_DEMO_CONSTRUCTOR.
  endclass.

data B type ref to B_CLASS.

start-of-selection.

  create object B.

try.
    B->BAR( ).
  catch CX_DEMO_CONSTRUCTOR.
    write 'Catching CX_DEMO_CONSTRUCTOR'.  "#EC NOTEXT
endtry.

class A_CLASS implementation.
  method FOO.
    raise exception type CX_DEMO_CONSTRUCTOR.
  endmethod.
endclass.

class B_CLASS implementation.
  method BAR.
    create object A.
    try.
        ...
        A->FOO( 'SOMETHING' ).
        ...
      catch CX_DEMO_ABS_TOO_LARGE.
        ...
    endtry.
  endmethod.
endclass.

Dieses Beispiel zeigt, welche Ausnahmen in der RAISING-Klausel der Methode BAR deklariert werden müssen. Es gibt in der Methode zwar keine Anweisung RAISE EXCEPTION, allerdings wird die Methode FOO gerufen, die laut ihrer Schnittstellenbeschreibung die Ausnahmen CX_DEMO_CONSTRUCTOR und CX_DEMO_ABS_TOO_LARGE liefern kann. CX_DEMO_ABS_TOO_LARGE wird explizit mit CATCH abgefangen, so daß lediglich CX_DEMO_CONSTRUCTOR die Schnittstelle passieren, weshalb diese Ausnahme in der RAISING-Klausel von BAR deklariert werden muß.

Ende des Inhaltsbereichs