ABAP: Funktionsbausteine entwickeln

Ein ABAP-Funktionsbaustein kapselt ein Stück Programmcode unter einem Namen und macht diesen Code global im SAP-System verfügbar. Funktionsbausteine sind immer Teil einer Funktionsgruppe. In vielen Fällen ist es aber besser, statt eines Funktionsbausteins eine Klasse (ABAP Objects) zu verwenden.

Wir schauen uns zunächst an, warum Modularisierung von Programmen wichtig ist. Danach untersuchen wir, wann ABAP-Funktionsbausteine sinnvoll sind, und wann nicht. Anschließend schauen wir uns die Aufrufsyntax und den Aufbau von Funktionsbausteinen an.

Warum solltest du deinen ABAP-Programmcode modularisieren?

Einer der wichtigsten Grundsätze guten Programmierens ist “never write the same code twice” . Wenn du an zwei Stellen im Programm dieselben Codezeilen schreibst, dann sei aufmerksam. Stell dir vor, du findest irgendwann später einen Fehler und korrigierst diesen. Jetzt ist ein Stück Code korrigiert, aber das andere ist noch immer falsch. Vielleicht hast du da noch den Überblick. Stell dir deshalb vor, jemand anderes muss deinen Programmcode später warten und weiß nichts von der Codedublette. Er findet einen Fehler, korrigiert diesen, und er hat kaum eine Chance zu wissen, dass derselbe Fehler noch an anderer Stelle schlummert.

Besser ist es daher, diese Funktionalität zu kapseln, einen Namen als Etikett draufzukleben und an beiden Stellen diesen einen Code aufzurufen. Jetzt gibt es nur noch eine Stelle mit dem Fehler. Es ist nur eine Stelle zu korrigieren. Die Qualität des Programmcodes ist besser geworden.

ABAP bietet verschiedene Möglichkeiten, solche Codedubletten zu vermeiden und einem Stück Programmcode einen Namen zu geben, unter dem es aufgerufen werden kann:

  • Formroutinen (diese sind offiziell obsolet)
  • Funktionsbausteine (darum geht es hier) und
  • globale Klassen und ihre Methoden. Diese sind in den meisten Fällen die bessere Wahl. Eine Klasse kann mehrere Methoden haben. Das ist übersichtlicher als eine größere Zahl von Funktionsbausteinen, die inhaltlich zusammengehören.

Wann solltest du Funktionsbausteine verwenden?

Betrachte neue Funktionsbausteine als etwas, wofür du eine Rechtfertigung brauchst, warum du sie überhaupt anlegst. Ich verweise dazu auf den Artikel Coding ABAP Like Java, der ganz ausdrücklich für mehr Objektorientierung in der ABAP-Entwicklung wirbt.

Gründe für Funktionsbausteine können sein:

  • Es gibt den Baustein schon, er muss angepasst werden, aber er soll ausdrücklich nicht in eine Klasse portiert werden.
  • RFC (remote function call): Man kann RFC-fähige Funktionsbausteine von extern (z.B. von einem SAP-PI) aufrufen, man kann sie als Webservice verfügbar machen. Das geht mit Klassen nicht. Ein Kundenprojekt, an dem ich arbeite, verwendet viel RFC, sehr viel RFC. Das bringt viele Funktionsbausteine mit sich. Aber das ist, wie gesagt, nur eine Ausnahme. Im Standardfall sollten neue Funktionsbausteine selten sein.
  • Verbucher: man kann Funktionsbausteine als Verbuchungsbausteine einsetzen. Das ist ein ganz eigenes Kapitel und würde hier den Rahmen sprengen.
  • Customer Exits: einige Customer Exits verlangen als Schnittstelle einen Funktionsbaustein mit einer bestimmten Signatur (Parameterliste). Bevor du so etwas neu machst, schaue dich bitte um, ob es andere Encancement-Möglichkeiten gibt, um dein Problem zu lösen, z.B. Badi.

Im weiteren Verlauf gehe ich davon aus, dass Du einen guten Grund für deinen Funktionsbaustein hast.

SAP-Funktionsbausteine aufrufen

SAP kommt mit einer großen Zahl von fertigen, einsetzbaren Funktionsbausteinen. Oft lohnt es sich, eine Weile zu suchen, ob es schon einen Baustein gibt, der dein Problem löst, als ihn selbst neu zu schreiben.

Zum Aufruf eines Funktionsbausteins gibt es eine besondere Syntax. Wie so oft hat SAP für diesen Zweck einen eigenen ABAP-Befehl geschaffen.

Solange du diese Syntax noch nicht auswendig weißt, gibt es eine Hilfestellung in der SAP-GUI für den korrekten Aufruf eines Funktionsbausteins. Auch dann, wenn du sie kennst, ist es oft von Vorteil, diesen Weg zu nutzen, weil dann die Namen aller zu übergebenden Parameter stimmen.

Du gehst in der SAP-GUI auf Menü/Bearbeiten/Muster:

Es erscheint der folgende Dialog. In diesem gibst du den Namen des Funktionsbausteins ein. Hier funktionieren auch Wildcards, z.B. „*ftp*“ mit nachfolgendem F4. Du erhältst dann eine Liste von Funktionsbausteinen, die vom Namen her passen und du wählst einen aus.

Drücke anschließend auf den grünen Haken. Die SAP-GUI erzeugt nun ein Aufrufmuster.

In diesem Muster sind alle Pflichtparameter in schwarz zu sehen, alle optionalen Parameter sind auskommentiert. Es ist nun deine Aufgabe, die Parameter zu füllen und ggf. dafür erst noch die benötigten Variablen einzurichten.

Du siehst Parameter, die vom Report an den Funktionsbaustein exportiert werden. Die Rückgabewerte des Funktionsbausteins werden importiert. Diese sind immer optional. Du kannst den Funktionsbaustein immer aufrufen, ohne dich für die Ergebnisse (und ggf. Fehlermeldungen) des Bausteins zu interessieren. Du solltest es in der Regel tun und dich für die Ergebnisse und die Fehler interessieren, aber die Aufrufsyntax erfordert es nicht.

Außerdem kann der Funktionsbaustein Ausnahmen (englisch: exceptions) werfen. Diese werden auf Fehlerwerte im sy-subrc gemappt. Wenn du eine Exceptions nicht aufgelistet hast und kein „other“ definiert hast, (other behandelt „alle anderen“) dann bricht das gesamte Programm hart ab und du findest den Abbruch in der Transaktion St22 wieder. Probiere das zur Übung gerne einmal aus.

Noch ein kurzer Hinweis zu importing und exporting:

In der Definition des Funktionsbausteins werden wir gleich noch sehen, dass die Daten, die von außen in den Funktionsbaustein hineingegeben werden, dort die Importparameter sind. Die Daten, die der Baustein zurückgibt, sind dort die Exportparameter. Das ist auch die natürliche Benennung. Beim Aufruf des Bausteins von außen ist die Benennung aber vertauscht. Der Report, das aufrufende Programm, exportiert Daten zum Funktionsbaustein und importiert die Ergebnisse. Das ist zu Anfang verwirrend. Wie vieles bei SAP muss man aber sagen: das ist bei SAP so. Man kann es nicht ändern.

Der Aufbau eines SAP-Funktionsbausteins

Als Beispiel für die Entwicklung eines eigenen Funktionsbausteins schauen wir uns einen Funktionsbaustein an, der ein wenig mit Zahlen rechnet. Natürlich würde man das normalerweise direkt in ABAP abhandeln und nicht an einen Funktionsbaustein delegieren. Es geht nur darum, dass der Funktionsbaustein irgend etwas zu tun bekommt.

Wir geben also zwei Zahlen in den Baustein hinein, dazu eine Operation (+,-,*,/) und bekommen eine Zahl als Ergebnis heraus.

Der Einstieg in die Transaktion SE37 zum Anlegen des Funktionsbausteins

Funktionsbausteine kann man in der SAP-GUI mit der Transaktion SE37 bearbeiten. (Es geht auch mit SE80, das sind aber dieselben Masken.)

Das Einstiegsbild in die Transaktion SE37 ist schlicht:

Du kannst den Namen eines Funktionsbausteins angeben, anschließend kannst du:

  • diesen anzeigen, sofern es ihn schon gibt
  • diesen ändern, sofern es ihn schon gibt
  • diesen anlegen, sofern es ihn noch nicht gibt
  • oder diesen löschen (das Icon mit der Tonne), sofern es ihn schon gibt.

Beim Anlegen erscheint anschließend dieser Dialog:

Du benötigst den Namen, die Funktionsgruppe und einen Kurztext, der an einigen Stellen im SAP neben dem Namen eingeblendet wird.

Falls du noch keine passende Funktionsgruppe hast: an dieser Stelle kannst du sie nicht anlegen. Wie das Anlegen funktioniert, steht weiter unten.

Der Name von eigenen Funktionsbausteinen soll mit Y_ oder Z_ beginnen. Z_ ist üblich. Andere Namen sind möglich, führen aber zu dieser Warnung:

Die Warnung erfolgt auch bei Z, wenn auf das Z nicht der Unterstrich erfolgt.

SAP lässt also einen Funktionsbausten HALLO_WELT zu. Das ist bei Reports in der Transaktion SE38 anders, da ist Z oder Y Pflicht, andernfalls wird nach einem SAP-Accesskey gefragt, den du bei SAP erst bestellen müstest.

Als nächstes kommst du in eine Eingabemaske, in der du deinen Funktionsbaustein bearbeiten kannst. Dieser Dialog ist in mehrere Karteireiter aufgeteilt: Eigenschaften, Import, Export, Changing, Tabellen, Ausnahmen, Quelltext.

Die Eigenschaften des Funktionsbausteins

Die Eingabemaske „Eigenschaften“ sieht so aus:

Die Karteikarte „Eigenschaften“ zeigt uns den Namen des Funktionsbausteins und die Funktionsgruppe (Name und Kurzbeschreibung), zu der er gehört. Auch das Paket ist zu sehen, dieses ergibt sich aus der Funktionsgruppe. In diesem Beispiel steht hier $TMP, das Beispiel ist als lokale Datei angelegt und geht nicht in die Transportkette ein.

Unter Ablaufart kann man einstellen, ob es sich

  • um einen normalen Funktionsbaustein handelt (das ist der Normalfall) oder
  • ob der Baustein remotefähig ist (RFC), also z.B. von einem PO-System direkt aufgerufen werden kann.
  • Daneben gibt es auch noch die Möglichkeit, den Baustein zu einem Verbuchungsbaustein zu erklären, mit vier verschiedenen Untervarianten. Das ist sehr speziell und sprengt hier den Rahmen.

In der Regel wird dein Funktionsbaustein ein normaler Baustein sein.

Der Baustein hat auch noch einen Verantwortlichen, einen Zeitstempel, und ein ABAP-Paket, zu dem er gehört.

Auf Basis der Funktionsgruppe hat er auch ein ABAP-Rahmenprogramm, zu dem er gehört und auch der technische Name des Includes, in dem er abgelegt wird, ist ein anderer als der Name, unter dem er aufrufbar ist. Mehr dazu unten unter Funktionsgruppe.

Die Importparameter des Funktionsbausteins

Auf der Karteikarte „Import“ kannst du die Eingabeparameter des Bausteins festlegen. Jedes Parameter hat einen Namen. Es bietet sich an, hier Namenskonventionen zu folgen (mehr dazu unten). Wenn alle Inputparameter mit i anfangen, dann ist im Programmcode immer ersichtlich, dass diese Variable von außen vorgegeben werden.

Die Typisierung ist entweder „type“ oder „type ref to“ (für Objekte). Dazu gehört dann noch der Bezugstyp. In unserem Falle also z.B. „type int4“.

Vorschlagswert ist der Defaultwert, der automatisch angenommen wird, wenn der Parameter optional ist und der Aufrufer keinen anderen Wert mitgibt.

Optional: ein Flag, das festlegt, ob der Aufrufer verpflichtet ist, dieses Parameter im Aufruf zu benennen und mit einem Wert zu füllen, oder ob er es auslassen darf. Wenn optional angekreuzt ist, darf der Aufrufer das Parameter weglassen.

Wertübergabe legt fest,

  • nicht angekreuzt: es wird direkt auf der Variable des Aufrufers gearbeitet. (Bitte dieser Variante den Vorzug geben)
  • angekreuzt: in der Parameterübergabe eine Kopie gezogen wird. Bei der Arbeit mit großen Tabellen handelt man sich damit ein Performanceproblem ein. In manchen Fällen ist diese Variante aber Pflicht, z.B. bei RFC. Bitte nur nutzen, wenn es zwingend erforderlich ist.

Die Exportparameter des Funktionsbausteins

Der Aufbau der Exportparameter ist nun nicht weiter überraschend. Wir sehen die Typisierung, den Bezugstyp, wir können wieder entscheiden, ob wir eine Wertübergabe machen. (Nein, machen wir nicht, außer wenn wir es müssen, wie bei RFC.)

Tabellenparameter einer Funktionsgruppe und changing-Parameter

Die Karteikarte „Tabellen“ bietet Tabellenparameter. Diese werden sowohl für import als auch für export verwendet und sind offiziell obsolet. Es gibt sie nur noch aus Gründen der Kompatibilität mit altem Code.

Neu sollen sie gar nicht verwendet werden, daher spare ich mir auch weitere Ausführungen dazu.

Changing-Parameter kann man verwenden, sie sind aber unüblich und ich halte sie für überflüssig. Dass ich Funktionsbausteine in Gänge für weitgehend überflüssig halte und meine, dass man lieber Klassen und Methoden schreiben sollte, hatte ich an anderer Stelle schon gesagt.

Die Ausnahmen eines Funktionsbausteins

Auf dem Reiter Ausnahmen können wir mögliche Ausnahmen definieren. Wenn wir den Aufruf eines Funktionsbausteins mittels Muster vornehmen, werden all diese Ausnahmen im generierten Code aufgelistet.

Dieses Ausnahmesystem ist den klassenbasierten Ausnahmen aus ABAP Objects unterlegen, ein Grund mehr, vermehrt auf Klassen und Methoden zu setzen. Dieses ältere Ausnahmesystem über Fehlercodes im sy-subrc ist bei Funktionsbausteinen aber Teil der Technik.

Die Namen der Ausnahmen können völlig frei bestimmt werden. Falls sie beim Aufrufer nicht aufgelistet (oder mit „other“ behandelt) werden, bricht das Programm ab und man findet diese Codes danach in ST22 in den Abbruchprotokollen.

Anders als bei klassenbasierten Ausnahmen können hier aber keine weiteren Informationen über den Abbruch mitgegeben werden.

Das Auslösen der Exception erfolgt im Programmcode des Bausteins über den ABAP-Befehl „raise“, gefolgt von der hier vergebenen Konstante. Achtung: der Name der Exception wird vom Compiler nicht geprüft. Wer sich hier verschreibt und eine Exception verwendet, die in der Signatur gar nicht vorgesehen ist, der erntet zur Laufzeit einen harten Abbruch mit der Information, dass die Ausnahme xxx im Funktionsbaustein gar nicht definiert ist.

Der Quelltext des Funktionsbausteins

Wir kommen jetzt zum wichtigsten Teil des Funktionsbausteins: zu seinem Quellcode.

Diese Karteikarte enthält einen ABAP-Editor. Die Schnittstelle des Funktionsbausteins findet sich hier als Kommentar. Man kann diesen Kommentar hier zwar löschen, sobald man aber auf eine andere Karteikarte wechselt und dann zum Quellcode zurückkommt, ist dieser Kommentar wieder da. Die eigentliche Schnittstelle kann man in der SAP-GUI im SE37 nicht bearbeiten. Man muss hierzu zwingend über die Dialoge auf den anderen Karteikarten gehen.

Das ist manchmal mühsam, wenn man z.B. umfangreiche Schnittstellen von einem Funktionsbaustein in einen anderen kopieren will.

Übrigens: im ABAP-Editor von Eclipse, den ich an dieser Stelle ausdrücklich empfehlen möchte, kann man die Schnittstelle hier unmittelbar im Quellcode bearbeiten.

Hier das komplette Beispiel unseres Rechenbausteins:

FUNCTION Z_HEV_TEST.
*"----------------------------------------------------------------------
*"*"Lokale Schnittstelle:
*"  IMPORTING
*"     REFERENCE(IF_NUM1) TYPE  INT4
*"     REFERENCE(IF_NUM2) TYPE  INT4
*"     REFERENCE(IF_OPERATION) TYPE  CHAR1
*"  EXPORTING
*"     REFERENCE(EF_RESULT) TYPE  INT4
*"  EXCEPTIONS
*"      DIVISION_BY_ZERO
*"      UNKNOWN_OPERATION
*"----------------------------------------------------------------------

if if_operation = '+'.
  ef_result = if_num1 + if_num2.
  return.
endif.

if if_operation = '-'.
  ef_result = if_num1 - if_num2.
  return.
endif.

if if_operation = '*'.
  ef_result = if_num1 + if_num2.
  return.
endif.

if if_operation = '/'.
  if if_num2 = 0.
    raise division_by_zero.
  endif.
  ef_result = if_num1 / if_num2.
  return.
endif.

raise UNKNOWN_OPERATION.

ENDFUNCTION.

Der Zusammenhang zwischen Funktionsbaustein und Funktionsgruppe

Wie oben schon erwähnt, gehört ein Funktionsbaustein immer zu einer Funktionsgruppe.

Für den Aufruf ist es egal, in welcher Funktionsgruppe, oder gar in welchem Paket sich der Baustein befindet. Wichtig ist es nur bei globalen Variablen, die man innerhalb der Funktionsgruppe in deren Top-Include definiert. Diese haben eine Gültigkeit innerhalb der gesamten Funktionsgruppe. Sie werden dann initialisiert, wenn ein Baustein der Funktionsgruppe aufgerufen wird. Alle folgenden Aufrufe von Bausteinen aus der Funktionsgruppe behalten diese globalen Variablen. Die Werte werden von einem Aufruf zum nächsten nicht verändert.

Hier eine Darstellung der Dateistruktur aus SE80:

Um nicht missverstanden zu werden: ich rede hier von globalen Variablen im Top-Include der Funktionsgruppe, nicht von Data-Deklarationen innerhalb des Funktionsbausteins. Letztere werden bei jedem neuen Aufruf der Funktionsgruppe neu initialisiert.

Diese globalen Variablen einer Funktionsgruppe werden überhaupt nur selten gebraucht und sie wären als statische Klassenvariablen einer globalen Klasse ohnehin besser angesiedelt. Es gibt also keinen sinnvollen Grund, globale Daten in ein Topinclude zu packen.

Die Dateistruktur einer Funktionsgruppe

Die Funktionsgruppe hat folgenden Dateiaufbau:

  • Rahmenprogramm: SAPL<funktionsgruppe>
  • Top-Include: L<funktionsgruppe>TOP. Hier können globale Daten der Funktionsgruppe stehen.
  • Funktionsbausteine: L<funktionsgruppe>U## mit der zweistelligen Zahl ##. Diese Dateien werden durchnummeriert, angefangen bei U01. Bei U99 ist Schluss. Damit kann eine Funktionsgruppe maximal 99 Funktionsbausteine enthalten. Wer viel mit Funktionsgruppen arbeitet, sollte also etwas vorausplanen und die Bausteine sinnvoll auf mehrere Funktionsgruppen verteilen. Ansonsten fängt das große Umorganisieren an, wenn man hier an den Anschlag kommt.
  • Eine zentrale Auflistung der Funktionsbausteine in L<funktionsgruppe>UXX. Hier steht buchstäblich XX. Diese Datei listet mit include lz<funktionsgruppe>u## einen Baustein nach dem anderen auf. Als Kommentar steht jeweils noch der Name des Funktionsbausteins dahinter.

In unserem Fall sieht das also so aus:

Dies hat eine direkte und schwierige Wirkung auf die Zusammenarbeit von mehreren Entwicklern:

Wer den eigenen Code eines neuen Funktionsbausteins transportiert, der transportiert damit auch das UXX-Include und muss damit auch alle neuen Funktionsbausteine der Entwicklerkollegen mittransportieren. Das führt gerne zu einer Verklumpung der Transporte.

Auch daher bietet es sich an, Funktionsbausteine (soweit inhaltlich sinnvoll) großzügig auf mehrere Funktionsgruppen zu verteilen.

Anlegen einer neuen Funktionsgruppe

Das Anlegen einer neuen Funktionsgruppe geht gut aus SE80, aber auch aus SE37 heraus.

In SE37 im Menü auf „Menü/Springen/Funktionsgruppenverwaltung/Gruppe anlegen“.

Es erscheint dann dieser Dialog:

Hier ist nur der Name der Funktionsgruppe anzulegen, samt einer kurzen Erklärung, wofür die Funktionsgruppe da ist. Es ist auch ein verantwortlicher Entwickler für die Gruppe zu bennen. Dies hat aber (soweit ich es bisher beobachtet habe) keine technischen Auswirkungen. Jeder Entwickler kann die Funktionsgruppe bearbeiten.

Namenskonventionen für die Parameter einer Funktionsgruppe

Für die Namen der Parameter sollte man sich Namenskonventionen halten, die im Projekt gelten. Bei uns gelten üblicherweise diese Präfixe:

  • if_ : Input einzelner Wert
  • is_ :Input einer Struktur
  • it_ : Input einer Tabelle
  • ef_, es_,et: entsprechend für Exportparameter

heiko

Dipl.-Ing. Heiko Evermann

Vorheriger Artikel