Funktionale Syntax in ABAP
Lesezeit ca. 5 Minuten
Die Programmiersprache ABAP hat schon ein paar Jahre auf dem Buckel. Um nicht komplett von moderneren Sprachen abgehängt zu werden, hat die SAP verschiedene neue Funktionen eingefügt. Wir wollen hier speziell Möglichkeiten der funktionalen Schreibweise vorstellen.
Dieser Artikel richtet sich hauptsächlich an ABAP-Entwickler.
Eine große Hürde beim Arbeiten mit Quellcode (ABAP oder nicht), ist das Verständnis, was in dem Code gerade passiert. Es hilft dabei, wenn der Quellcode nicht zu lang wird, damit man die Logik möglichst auf einem Blick sehen kann. Leider ist gerade das Kriterium „nicht zu lang“ traditionell keine Stärke von ABAP.
Warum ist ABAP-Code oft lang
Ein Grund dafür ist, dass die traditionellen ABAP-Befehle in sich abgeschlossen sind und nicht als Bestandteil eines Ausdrucks verwendet werden können. Dies erfordert viele Hilfsvariablen, die Informationen von einem Befehl zum Nächsten transportieren.
Als Beispiel nehme ich die Prüfung, ob eine interne Tabelle mehr als 10 Zeilen hat. Falls ja, soll ein Text ausgegeben werden. Dies könnte man folgendermaßen implementieren (der Umweg mit den CONCATENATE soll es ermöglichen, die Anzahl der Zeilen auszugeben, ohne dass vor der Zahl viele Leerzeichen stehen):
DATA: gv_table_lines TYPE int4,
gv_lines_char TYPE char10,
gv_text TYPE char50.
DESCRIBE TABLE gt_table LINES gv_table_lines. " Diese Tabelle sei bereits gefüllt
IF gv_table_lines > 10.
gv_lines_char = gv_table_lines.
CONCATENATE 'Die Tabelle hat' gv_lines_char 'Zeilen'
INTO gv_text SEPARATED BY space.
CONDENSE gv_text.
WRITE: gv_text.
ENDIF.
Für eine einfache Aufgabe ist das ganz schön viel Code. Die Definition der Hilfsfelder, das Formatieren des Textes aber auch das IF – ENDIF kosten einiges an Platz.
Wie können funktionale Ausdrücke helfen
Eine Möglichkeit, die Länge von traditionellem Code zu reduzieren, ist die Verwendung von funktionalen Ausdrücken. Damit können ABAP-Befehle innerhalb von anderen ABAP-Befehlen aufgerufen werden. Das Beispiel von oben sähe damit so aus (der Zeilenumbruch entsteht hier nur durch den Blog-Editor …):
WRITE COND #( WHEN lines( gt_table ) > 10 THEN |Die Tabelle hat { lines( gt_table ) } Zeichen| ).
Ob es immer der Lesbarkeit dient, eine komplexe Logik in einer einzelnen langen Zeile auszudrücken, lassen wir mal dahingestellt. Aber es ist möglich, die 11 Zeilen lange Coding-Strecke von oben mit nur einer Zeile zu implementieren.
Man erreicht diese Reduktion hier damit, dass man
- die Ermittlung der Zeilenanzahl,
- die Prüfung „über 10 oder nicht“ und
- die Zusammenstellung eine Texts zur Ausgabe
ineinander verschachtelt – und in Gänze als Argument der WRITE-Anweisung verwendet.
Was gibt es alles für Funktionen
Seit dem Netweaver Release 7.02 sind nach und nach immer mehr Elemente in die Sprache eingeführt worden, die in der funktionalen Form verwendet werden können. Im Folgenden wollen wir hier einige vorstellen. Diese Vorstellung ist dabei nicht vollständig. Viele der vorgestellten Befehle haben noch weitere Funktionen oder es gibt ähnliche Befehle, die weitere Möglichkeiten bieten. Aber eine vollständige Darstellung würde den Rahmen dieses Blogs sprengen – und selbst wenig übersichtlich sein.
Variablen und Objekte
Inline-Deklaration
Der Übersicht halber sollten lokale Variablen am Anfang z.B. einer Methode deklariert werden. Dies kann zu einem längeren Block von DATA-Anweisungen führen. Inzwischen gibt es aber auch die Möglichkeit, die Variable bei der ersten Verwendung zu definieren. Hilfreich ist das z.B. bei Arbeitsbereichen einer Schleife.
Implementierung mit alter Syntax:
Separate Variablendeklaration
DATA: workarea LIKE LINE OF internal_table.
LOOP AT internal_table INTO workarea.
WRITE: workarea-field.
ENDLOOP.
Beispiel für die neue Syntax:
Inline-Deklaration
LOOP AT internal_table INTO DATA(workarea).
WRITE: workarea-field.
ENDLOOP.
Diese Form sollte aber tatsächlich nur angewendet werden, wenn die inline-deklarierte Variable nur in einem eng begrenzten Bereich (z.B. nur innerhalb einer Schleife) verwendet wird. Bei Variablen die an mehreren Stellen eines längeren Stück Codings verwendet werden, dürfte die Deklaration am Anfang zu mehr Übersicht führen.
(SAP-Dokumentation „Inline Deklaration“)
Instanziierung mit NEW
Mit dem Schlüsselwort NEW können Sie Instanzen von Klassen oder Datenreferenzen anlegen. So kann man sie entweder als Argument eines Ausdrucks verwenden oder in der gleichen Zeile noch auf Methoden oder Attribute zugreifen.
Implementierung mit alter Syntax:
(Der Code liest hier die Personalnummer des angemeldeten Benutzers )
Instanziierung mit CREATE OBJECT
DATA: lv_pernr TYPE pernr_d,
lo_hress TYPE REF TO cl_hress_employee_services.
CREATE OBJECT lo_hress.
lv_pernr = lo_hress->get_pernr( ).
Beispiel für die neue Syntax:
Instanziierung mit NEW
DATA: lv_pernr TYPE pernr_d.
lv_pernr = NEW CL_HRESS_EMPLOYEE_SERVICES( )->get_pernr( ).
(SAP-Dokumentation „Instanziierung mit NEW“)
Prüfungen / Vergleiche
boolc
Man kann die ABAP-Funktion „boolc“ verwenden, wenn das Ergebnis einer Abfrage entweder wahr oder falsch sein kann. Dies spart die Ausprogrammierung über IF – ELSE – ENDIF.
In diesem Beispiel benötigt eine Methode zum Anzeigen von Dokumentation als Parameter die Information, ob eine Standardfunktion GUI-Popups öffnen darf („no_dialog“). Diese Information kann man anhand des Kennzeichens SY-BATCH während der Parameterübergabe ermitteln.
Implementierung mit alter Syntax:
Prüfung per IF-ELSE
PARAMETERS word TYPE c length 30.
DATA result_tab TYPE cl_abap_docu=>search_results.
DATA lv_no_diag TYPE flag.
IF sy-batch IS NOT INITIAL.
lv_no_diag = abap_true.
ENDIF.
cl_abap_docu=>start(
EXPORTING word = word
no_dialog = lv_no_diag
IMPORTING search_results = result_tab ).
Beispiel für die neue Syntax:
boolc-Funktion als Parameter
PARAMETERS word TYPE c length 30.
DATA result_tab TYPE cl_abap_docu=>search_results.
cl_abap_docu=>start(
EXPORTING word = word
no_dialog = boolc( sy-batch IS NOT INITIAL )
IMPORTING search_results = result_tab ).
(SAP-Dokumentation „boolc“).
Etwas kniffelig ist die Tatsache, dass die Funktion BOOLC als Ergebnis einen String liefert. Wenn in Schnittstellen ein Character-Feld gefordert ist, muss das Ergebnis evtl. noch konvertiert werden (s.u.).
cond
Falls eine Prüfung nicht nur zwischen wahr und falsch sondern zwischen komplexeren Sachverhalten unterscheiden soll, kann man die Funktion „cond“ verwenden.
Implementierung mit alter Syntax:
(hier fragen wir die Datenbank ab und tragen ‚ANY‘ in ein Feld ein, wenn es sich nicht um eine HANA-Datenbank handelt)
Prüfung per IF
DATA: lv_database_type TYPE string.
IF sy-dbsys NS 'HANA' .
lv_database_type = 'ANY'
ENDIF.
Beispiel für die neue Syntax:
Prüfung per COND
DATA: lv_database_type TYPE string.
lv_database_type = COND #( WHEN sy-dbsys NS 'HANA' THEN 'ANY' ).
Diese Funktion haben wir auch im Eingangsbeispiel verwendet.
Numerische Funktionen / Beschreibungsfunktionen
lines / strlen
Die Länge von Strings und Tabellen wird häufig in anderen Ausdrücken weiterverarbeitet. Funktionen wie „lines“ (für die Anzahl von Tabellenzeilen) oder „strlen“ (für die Anzahl Zeichen in einem String) helfen dabei, Zwischenschritte zu sparen
Implementierung mit alter Syntax bei Tabellen:
(Bei Strings gibt es keinen „traditionellen“ ABAP-Befehl, um die Länge zu ermitteln)
Beschreibung von Tabellen per DESCRIBE
DATA lt_table TYPE stringtab.
DATA lv_num TYPE int4.
“ Beispiel für die Länge von Tabellen
APPEND ‚Hallo‘ TO lt_table.
APPEND ‚Welt‘ TO lt_table.
DESCRIBE TABLE lt_table LINES lv_num.
WRITE lv_num. “ Das Ergebnis ist: 2
Beispiel für die neue Syntax:
Beschreibung von Tabellen / Strings per lines / strlen
DATA lv_string TYPE string VALUE 'Hallo Welt'.
DATA lt_table TYPE stringtab.
“ Beispiel für die Länge von Strings
WRITE strlen( lv_string ). “ Das Ergebnis ist: 10
“ Beispiel für die Länge von Tabellen
APPEND ‚Hallo‘ TO lt_table.
APPEND ‚Welt‘ TO lt_table.
WRITE lines( lt_table ). “ Das Ergebnis ist: 2
(SAP-Dokumentation „strlen“, SAP-Dokumentation „lines“)
nmax
Die größere von zwei Zahlen können wir mit der Funktion ’nmax‘ ermitteln.
Implementierung mit alter Syntax:
Auswahl der größeren Zahl durch IF
DATA lv_num1 TYPE int4 VALUE 12.
DATA lv_num2 TYPE int4 VALUE 9.
DATA lv_result TYPE int4.
IF lv_num1 > lv_num2.
lv_result = lv_num1.
ELSE.
lv_result = lv_num2.
ENDIF. " Das Ergebnis ist: 12
Beispiel für die neue Syntax:
Auswahl der größeren Zahl durch nmax
DATA lv_num1 TYPE int4 VALUE 12.
DATA lv_num2 TYPE int4 VALUE 9.
DATA lv_result TYPE int4.
lv_result = nmax( val1 = lv_num1 val2 = lv_num2 ). " Das Ergebnis ist: 12
Für die kleinere von zwei Zahlen gibt es die Funktion ’nmin‘, diese funktioniert genauso wie die ’nmax‘.
Konvertierungen und Wertzuweisungen
Konvertierung mit CONV
Konvertierungen von einem Datentyp in einen anderen (z.B. String in Character) sind immer eine lästige Aufgabe. Sie beinhalten keine wichtige Logik, sind aber erforderlich um das Ergebnis einer Logik für die weitere Verarbeitung zur Verfügung zu stellen. Mit dem ABAP-Befehl CONV können Werte bei der Parameterübergabe in einen anderen Datentyp konvertiert werden.
In folgendem Beispiel soll ein Infotyp aus dem Organisationsmangement gelesen werden. Die Objekt-ID liegt aber nur in der Form REALO vor (45-stellig). Wenn wir die ID in diesem Format an den Funktionsbaustein übergeben, kommt es sofort zu einem Absturz.
Implementierung mit alter Syntax:
(Der Wert muss mit Hilfe einer Hilfsvariable konvertiert werden)
Konvertierung per Hilfsvariable
DATA lv_realo TYPE realo VALUE
'50000050'
.
DATA lv_objid TYPE hrobjid.
DATA lt_p1028 TYPE TABLE OF p1028.
" Konvertierung per Hilfsvariable
lv_objid = lv_realo.
CALL FUNCTION
'RH_READ_INFTY'
EXPORTING
plvar =
'01'
otype =
'O'
objid = lv_objid " Übergabe der Hilfsvariable
" Wenn hier direkt lv_realo übergeben wird, stürzt der Baustein ab
infty =
'1028'
TABLES
innnn = lt_p1028
EXCEPTIONS
OTHERS =
6
.
Beispiel für die neue Syntax – die Konvertierung kann direkt bei der Parameterübergabe stattfinden:
Konvertierung von Parametern per CONV
DATA lv_realo TYPE realo VALUE
'50000050'
.
DATA lt_p1028 TYPE TABLE OF p1028.
CALL FUNCTION
'RH_READ_INFTY'
EXPORTING
plvar =
'01'
otype =
'O'
objid = CONV HROBJID( lv_realo ) " Konvertierung bei Parameterübergabe
infty =
'1028'
TABLES
innnn = lt_p1028
EXCEPTIONS
OTHERS =
6
.
Auf diese Weise kann man auch zwischen ganzen Zahlen und Kommazahlen konvertieren oder zwischen Strings und Character-Feldern.
Wertzuweisung per VALUE
Wenn einzelne Werte zugewiesen werden sollen, kann man das einfach mit der Anweisung ‚=‘ machen. Bei Strukturen führt das zu längeren Abschnitten, die relativ gleichförmig sind, da wir Werte Feld für Feld zuweisen müssen.
Mit dem Operator VALUE kann man in einem Befehl mehrere Felder in eine Struktur füllen – und bei Bedarf das Ergebnis gleich an eine Tabelle anhängen. In folgendem Beispiel füllen wir eine Tabelle von Typ SYMSG (Systemnachrichten).
Implementierung mit alter Syntax:
Füllen einer Tabelle mit APPEND
DATA lt_symsg TYPE TABLE OF symsg.
DATA ls_symsg TYPE symsg.
“ Anhängen der ersten Zeile
ls_symsg-msgty = ‚E‘.
ls_symsg-msgid = ‚DEMO‘.
ls_symsg-msgno = ‚001‘.
APPEND ls_symsg TO lt_symsg.
“ Anhängen der zweiten Zeile
CLEAR ls_symsg. “ Wer weiß, welche Felder sonst noch gefüllt waren …
ls_symsg-msgty = ‚E‘.
ls_symsg-msgid = ‚DEMO‘.
ls_symsg-msgno = ‚002‘.
APPEND ls_symsg TO lt_symsg.
Beispiel für die neue Syntax:
Füllen einer Tabelle mit VALUE
DATA lt_symsg TYPE TABLE OF symsg.
DATA ls_symsg TYPE symsg.
“ Direktes Befüllen einer Struktur
ls_symsg = VALUE #( msgty = ‚E‘ msgid = ‚DEMO‘ msgno = ‚001‘ ).
APPEND ls_symsg TO lt_symsg.
“ Direktes Befüllen einer Tabelle mit einer zweiten Zeile
lt_symsg = VALUE #( BASE lt_symsg ( msgty = ‚E‘ msgid = ‚DEMO‘ msgno = ‚002‘ ) ).
Methodenaufrufe
Es gibt in ABAP verschiedene Arten, objektorientiert Methoden aufzurufen. Eine Möglichkeit ist der Befehl CALL METHOD, mit dem sich Methoden aufrufen lassen wie Funktionsbausteine. Diesen Befehl kann man weglassen, und die Methode direkt über (Klassenname)->(Methodenname) aufrufen.
In dieser (funktionalen) Schreibweise lassen sich verschiedene Methodenaufrufe auch direkt miteinander verknüpfen – und der entsprechende Code häufig dramatisch verkürzen.
Beispiel: Aufruf über CALL METHOD (in diesem Beispiel ermitteln wir den Namen eines Mitarbeiter über die Klasse CL_PT_EMPLOYEE):
Methodenaufruf über CALL METHOD
DATA: lo_employee TYPE REF TO if_pt_employee,
lv_ename TYPE emnam.
" Instanz erzeugen
CALL METHOD cl_pt_employee=>get_employee
EXPORTING
i_pernr =
'00000028'
RECEIVING
r_employee = lo_employee.
" Name ermitteln
CALL METHOD lo_employee->get_ename
RECEIVING
r_ename = lv_ename.
WRITE lv_ename.
Beispiel: Aufruf per funktionaler Schreibweise (und schon deutlich kompakter)
Methodenaufruf (funktionale Schreibweise)
DATA: lo_employee TYPE REF TO if_pt_employee,
lv_ename TYPE emnam.
" Instanz erzeugen
lo_employee = cl_pt_employee=>get_employee( i_pernr =
'00000028'
).
" Name ermitteln
lv_ename = lo_employee->get_ename( ).
WRITE lv_ename.
Beispiel: Aufruf per verknüpfter funktionaler Schreibweise
Methodenaufruf (verknüpfte funktionale Schreibweise)
DATA: lv_ename TYPE emnam.
“ Instanz erzeugen und Name ermitteln
lv_ename = cl_pt_employee=>get_employee( i_pernr = ‚00000028‘ )->get_ename( ).
“ Ergebnis ausgeben
WRITE lv_ename.
“ oder noch kürzer und in einer Zeile:
WRITE cl_pt_employee=>get_employee( i_pernr = ‚00000028‘ )->get_ename( ).
Die Verknüpfung von Methodenaufrufen in funktionaler Schreibweise ist allerdings nur möglich, wenn jede Methode genau einen Returning-Parameter besitzt. In Kombination mit Exporting- und Changing-Parametern kann man Methodenaufrufe nicht verknüpfen.
Fazit
Das Schreiben von lesbarem und wartbarem Code wird immer eine Herausforderung bleiben. Und wenn man die hier vorgestellten Funktionen so nutzt, dass man ganz viel Logik in einer einzelnen Zeile ausdrückt, hat sicher keiner etwas gewonnen.
Aber sie bieten doch die Möglichkeit, Routinetätigkeiten zu komprimieren. So ist mehr Code „auf einen Blick“ zu sehen und langes Scrollen entfällt. Durch das Wegfallen von Hilfsvariablen sinkt auch das Risiko, dass das Initialisieren dieser Variablen vergessen wird (typischer Fehler: Es fehlt ein CLEAR).
Vielleicht (zugegeben ein gewagter Vorschlag) kann man die verkürzte Zeilenzahl auch dazu nutzen, den einen oder anderen zusätzlichen Kommentar unterzubringen. Nur falls Sie pro Zeile Quellcode bezahlt werden, vergessen Sie besser alles, was hier geschrieben steht.