Cheat Sheet - Modernes ABAP

Cheat Sheet Modernes ABAP

Dieses Plakat ist die perfekte Ergänzung zu den Schulungen

von Brandeis Consulting

Die Sprache ABAP hat sich seit NetWeaver ABAP 7.40 enorm weiterentwickelt. Viele neue Sprachkonstrukte sind hinzugekommen. Vor allem natürlich viele neue Ausdrücke. Damit kann man sauberen Code schreiben und sich viele Hilfsvariablen und Deklarationen sparen. Die neuen Sprachelemente sollten genutzt werden, um den Code robuster und lesbarer zu machen. Eine wesentlich schnellere Ausführung ist jedoch nicht zu erwarten.

Dieses Plakat zeigt die wichtigsten und nützlichsten Neuerungen. Ausgelassen wurden die neue ABAP-SQL-Syntax und die ABAP Built-In Functions.

Inline-Deklarationen

Mit Inline-Deklarationen kann eine Variable dort definiert werden, wo sie erstmals verwendet wird – also bei ihrer ersten Wertzuweisung. An vielen Stellen im ABAP-Code ergibt sich der Datentyp einer Variablen aus dem Kontext.

Mit DATA(<Name>) kann ein Datenobjekt dort definiert werden, wo es benötigt wird.

Modernes ABAP

LOOP AT ResultTab INTO DATA(ResultLine).
    ...
ENDLOOP.


Klassisches ABAP

DATA ResultLine LIKE LINE OF ResultTab.

LOOP AT ResultTab INTO ResultLine.
    ...
ENDLOOP.

Die Vorteile der Inline-Deklaration sind neben dem kompakteren Code auch die Flexibilität. Wenn sich im Beispiel die Feldliste ändert, wird der Datentyp von RESULT automatisch angepasst. Gerade bei JOIN Operationen hat man oft auch keine passende Strukturdefinition.

Modernes ABAP

SELECT *
  FROM zbc_users
 INTO TABLE @DATA(Result).


Klassisches ABAP

DATA Result TYPE TABLE OF zbc_users.

SELECT *
  FROM zbc_users
 INTO TABLE Result.

Auch Feldsymbole können mit Inline-Deklarationen einem existierenden Speicherbereich zugewiesen werden. Entweder mit einem ASSIGN oder mit den entsprechenden Anweisungen für den Zugriff auf interne Tabellen wie zum Beispiel

LOOP AT ... ASSIGNING FIELD-SYMBOL(<fs-name>).
...
ENDLOOP." oder

READ TABLE ... ASSIGNING FIELD-SYMBOL(<fs-name>) ...

Schreibgeschützte Datenobjekte mit FINAL

Sehr häufig wird Datenobjekte nur einmal ein Wert zugewiesen, der sich im folgenden Code nicht ändern soll. In diesen Fällen kann statt mit DATA auch mit FINAL deklariert werden. Damit kann man sich darauf verlassen, dass immer dieser Wert zugewiesen ist und nicht versehentlich etwas anderes eingetragen wird.

FINAL(Tomorrow) = CONV dats( sy-datum + 1 ).

SAP Dokumentation

Verkettungsoperator &&

Zwei Zeichenketten können mit dem Operator && verkettet werden. Dies ist wesentlich eleganter als mit CONCATENATE und funktioniert auch an Operandenpositionen ohne Hilfsvariable.

Modernes ABAP

out->write( `Es ist das Jahr `
                && sy-datum(4) ).


Klassisches ABAP

DATA tmp TYPE c LENGTH 100.
CONCATENATE `Es ist das Jahr `
            sy-datum(4) INTO tmp.
out->write( tmp ).

Berechnungszuweisung mit &&=

Beim Zusammensetzen von Zeichenketten ist es oft notwendig, 'Anhängen' zu verwenden. Zum Beispiel bei der Generierung von HTML.

Modernes ABAP

html &&= `<b>Hallo</b>`

Klassisches ABAP

CONCATENATE html `<b>Hallo</b>`
       INTO html.

String-Templates

String-Templates sind Ausdrücke, die eine Zeichenkette erzeugen. Sie bestehen aus Literalen, die eingebettete Ausdrücke, Formatierungen und Steuerzeichen enthalten können. Ein String-Template beginnt und endet mit einem senkrechten Strich |. Alles dazwischen ist konstanter Text (Literal), außer er ist ein von geschweiften Klammern { ... } umgebener, eingebetteter Ausdruck.

Die Zeichen {, }, | und \ müssen im Literal durch einen Backslash \ escaped werden.

Modernes ABAP

out->write( |Heute: {
    sy-datum DATE = USER }| ).





Klassisches ABAP

DATA tmp   TYPE string.
DATA datum TYPE c LENGTH 10.

WRITE sy-datum TO datum.
CONCATENATE `Heute: ` datum INTO tmp.

out->write( tmp ).

Ausdrücke in String Templates

In geschweiften Klammern { ... } können Ausdrücke direkt in das String-Template eingebettet werden. Damit kann z.B. eine funktionale Methode aufgerufen oder eine Berechnung durchgeführt werden. Schöne Beispiele dafür sind bei den Konstruktor-Ausdrücken COND und SWITCH.

Formatierung

Die Darstellung der Ausdrücke kann über die Formatierungsoptionen angepasst werden. Die wichtigsten sind

  • WIDTH = <Länge> - Breite in Zeichen
  • ALIGN = <Ausrichtung> - Entweder LEFT, RIGHT oder CENTER
  • DATE = USER - Formatierung des Datums gemäß Benutzerstammsatz, siehe oben
  • TIME = USER - Dasselbe für die Zeit
  • ALPHA = IN/OUT - Alphakonvertierung

Steuerzeichen in String Templates

Die Steuerzeichen

  • \n - Zeilenvorschub/Line Feed
  • \r - Wagenrücklauf/Return und
  • \t - Tabulator

können direkt in String Templates verwendet werden. Damit können also ohne Weiteres auch mehrzeilige Texte ohne die Verwendung von CL_ABAP_CHAR_UTILS=>CR_LF erzeugt werden.

out->write( |Hallo\r\nWelt| ).

Konstruktor-Ausdrücke

Konstruktor-Ausdrücke erzeugen ein neues (Daten-) Objekt eines bestimmten (Daten-) Typs. Dazu werden sogenannte Konstruktor-Operatoren verwendet. Diese decken eine Vielzahl sehr unterschiedlicher Anwendungsfälle ab. Gemeinsam haben sie jedoch die folgende Syntax:

<Konstruktor-Operator> <Datentyp>( <Parameter> ).

Ein einfaches Beispiel: Mit dem Operator VALUE eine interne Tabelle mit festem Inhalt erzeugen.

TYPES tt_Country TYPE STANDARD TABLE OF I_Country WITH DEFAULT KEY.
DATA Country TYPE tt_Country.                  

Country = VALUE tt_Country( ( Country = 'DE' IsEuropeanUnionMember = 'X' )
                            ( Country = 'US' IsEuropeanUnionMember = ' ' ) ).

Der Datentyp und das Hash-Zeichen #

Je nach Situation wird der passende Datentyp angegeben, z.B. ein Datenelement, ein Tabellentyp, ein Strukturtyp oder ein Klassenname.

Wenn der Datentyp aus dem Kontext heraus abgeleitet werden kann, dann muss er nicht mehr angegeben werden. Stattdessen wird das # Zeichen verwendet. Das ist z.B. bei einer Zuweisung der Fall.

Lesbarkeit von Konstruktor-Ausdrücken

Einige Operatoren können sehr komplexe Logik ausführen. Dazu können Konstruktor-Ausdrücke auch ineinander verschachtelt werden. Der Code wird dabei schnell unübersichtlich. Es empfiehlt sich daher, nur einfache Logik in Konstruktor-Ausdrücken zu verwenden. Bei komplexer Logik ist klassisches ABAP oft besser lesbar. In den ADTs gibt es keine Möglichkeit, Konstruktor-Ausdrücke zu debuggen.

Modern ABAP Overview

Brandeis Consulting

Maßgeschneiderte Schulungen und Beratung von Buchautoren und SAP Champions! Auf unserer Website www.brandeis.de finden Sie unser Angebot.

(C) Brandeis Consulting GmbH

Daten und Objekte erzeugen mit VALUE und NEW

Die beiden Operatoren erzeugen Daten. Mit VALUE erhält man die Daten direkt, mit NEW die Referenz darauf. Wenn keine Parameter übergeben werden, erzeugen beide Operatoren leere Datenobjekte.

Strukturen erzeugen

Wenn Strukturen erzeugt werden, kann man die einzelnen Komponenten als Parameter mitgeben. Nicht zugewiesene Komponenten werden mit ihrem Initialwert belegt.

DATA(Country) = VALUE I_Country( Country = 'DE'  IsEuropeanUnionMember = 'X' ).

Interne Tabellen erzeugen

Die einzelnen Zeilen einer internen Tabelle werden wiederum in runde Klammern eingeschlossen.

DATA DateRange TYPE RANGE OF dats.

DateRange = VALUE #( ( sign = 'I' option = 'EQ'   low = '20221031'  )
                     ( sign = 'I' option = 'EQ'   low = '20220406'  ) ).

Gemeinsame Bestandteile der einzelnen Zeilen können auch vor den runden Klammern definiert werden.

DateRange = VALUE #( sign = 'I' option = 'EQ'  ( low = '20221031'  )
                                               ( low = '20230406'  ) ).

BASE gibt einen Initialwert vor

Bei Strukturen kann vor der ersten Komponente mit BASE eine kompatible Struktur angegeben werden, die die nicht zugewiesenen Komponenten mit Werten füllt.

Bei internen Tabellen kann mit BASE eine interne Tabelle mitgegeben werden, die dann durch die folgenden Zeilen ergänzt wird. Das entspricht also einem APPEND.

NewDateRange = VALUE #( BASE DateRange
                      ( sign = 'I' option = 'EQ'   low = '20230101'  ) ).

Im VALUE-Operator sind auch FOR-Schleifen möglich. Diese sind beim REDUCE-Operator beschrieben.

Objekte erzeugen mit NEW

Mit NEW können auch Instanzen von Klassen erzeugt werden.

NEW <Klassenname>( <Konstruktorparameter> )

SAP Dokumentation VALUE und NEW

Datentypänderung mit CONV und EXACT

Dieser Operator wird verwendet, um Datentypen zu konvertieren. Dies ist z.B. bei Methodenparametern ein häufiges Problem: Der Inhalt einer Variablen passt, der Datentyp aber nicht.

Modernes ABAP

my_method( Start = CONV #( sy-datum ) ).
"oder
my_method( Start = EXACT #( sy-datum ) ).

Klassisches ABAP

DATA DateText TYPE char8.
DateText = sy-datum.
my_method( Start =  DateText )

Der Unterschied zwischen EXACT und CONV ist erst im Falle von Datenverlust erkennbar. Wenn durch die Typkonvertierung beispielsweise der Inhalt einer Zeichenkette abgeschnitten wird, dann führt CONV zur Laufzeit zu einer Ausnahme vom Typ CX_SY_MOVE_CAST_ERROR, die abgefangen werden kann.

Down- und Upcast von Referenzvariablen mit CAST

In klassischem ABAP erfolgt der Upcast durch Zuweisung mit =, der Downcast mit dem Cast-Operator ?=. Dafür wird immer eine Hilfsvariable vom passenden Typ benötigt. Mit dem CAST-Operator kann diese in vielen Fällen entfallen.

Modernes ABAP

DATA User TYPE zbc_user.

DATA(UserComponents) = cast cl_abap_structdescr(
                          cl_abap_typedescr=>describe_by_data(
                              User ) )->get_components(  ).

Klassisches ABAP

DATA User TYPE zbc_users.
DATA StructDescr TYPE REF TO cl_abap_structdescr.
DATA(TypeDescr) = cl_abap_typedescr=>describe_by_data( User ).

StructDescr ?= TypeDescr.
DATA(UserComponents) = StructDescr->get_components(  ).

Fallunterscheidungen mit COND und SWITCH

Diese Konstruktor-Ausdrücke entsprechen in SQL dem CASE Ausdruck. Einfache Fallunterscheidungen in Abhängigkeit von einem Feld werden mit SWITCH implementiert.

Modernes ABAP

out->write(
  |Hallo { SWITCH #( User-Gender
                    WHEN 'F' THEN 'Frau'
                    WHEN 'M' THEN 'Herr'
                    ELSE '' )
              } { User-Lastname } | ).




Klassisches ABAP

DATA Salutation TYPE string.

CASE User-Gender.
  WHEN 'F'.    Salutation = 'Frau'.
  WHEN 'M'.    Salutation = 'Herr'.
  WHEN OTHERS. Salutation = ''.
ENDCASE.

out->write(  |Hallo { Salutation
                  } { User-Lastname }| ).

Komplexere Unterscheidungen mit beliebigen Bedingungen erfolgen mit COND.

out->write( |Der Status ist { COND #( WHEN Priority > 3
                                       AND DueDate <= SY-DATUM  THEN 'Critical'
                                      WHEN Priority > 2         THEN 'Medium'
                                                                ELSE 'Low' ) }| ).

SAP Dokumentation COND und SWITCH

Mit REDUCE einen Wert aus einer internen Tabelle berechnen

Dieser Operator berechnet durch Schleifen über interne Tabellen einen einzelnen Ergebniswert. Auf komplexe Varianten wurde bewusst verzichtet, da dieser Operator schnell die Lesbarkeit einschränkt.

out->write( REDUCE string( INIT res TYPE string
                                sep TYPE string
                            FOR User IN Users
                            NEXT
                              res &&= sep && User-Firstname
                              sep = ', ' ) ).

Interne Tabellen mit dem FILTER-Operator erzeugen

Mit dem FILTER-Operator können interne Tabellen auf Basis einer anderen internen Tabelle durch Filterung erzeugt werden. Entweder über eine einfache WHERE-Klausel oder anhand einer anderen Tabelle. Grundsätzlich müssen Tabellentyp und -Schlüssel für die Filterung optimiert sein, sonst gibt es Syntaxfehler.

FILTER mit WHERE-Klausel

DATA lt_data TYPE SORTED TABLE OF I_CountryText WITH UNIQUE KEY LANGUAGE COUNTRY.
SELECT * FROM i_countrytext INTO TABLE @lt_data.

out->WRITE( FILTER #( lt_data WHERE LANGUAGE = 'D' ) ).

FILTER mit IN ... WHERE

Hier wird nach einer anderen internen Tabelle gefiltert. Dies entspricht einem INNER JOIN in SQL.

DATA lt_data TYPE SORTED TABLE OF I_CountryText WITH UNIQUE KEY COUNTRY.
DATA(lt_filter) = VALUE tt_demo( ( COUNTRY = 'DE' )
                                 ( COUNTRY = 'US' ) ).
SELECT * FROM i_countrytext WHERE LANGUAGE = 'D' INTO TABLE @lt_data.

out->WRITE( FILTER #( lt_data IN lt_filter WHERE COUNTRY = COUNTRY  ) ).

Der Operator CORRESPONDING

Dieser Operator erinnert an die Anweisung MOVE-CORRESPONDING. Er kann sowohl für Strukturen als auch für interne Tabellen verwendet werden. Mit dem CORRESPONDING-Operator kann man aus einem strukturierten Datenobjekt, also einer internen Tabelle oder einer Struktur, ein anderes Datenobjekt erzeugen und dabei die Werte gleichlautender Felder übernehmen. Das ähnelt der Anweisung MOVE-CORRESPONDING.

Grundform von CORRESPONDING

Die Daten werden in gleichlautende Felder kopiert. Felder, die in der Quelle nicht vorhanden sind, bleiben im Ziel leer.

TasksSmall = corresponding #( TasksOriginal ).

Explizites MAPPING und EXCEPT

Wenn die Komponenten nicht exakt den gleichen Namen haben und trotzdem aufeinander kopiert werden oder wenn einzelne Komponenten eben nicht kopiert werden sollen, so kann dies explizit mit den Zusätzen MAPPING und EXCEPT angegeben werden.

TaskSmall = corresponding #( TaskOriginal
                                MAPPING id    = task_id
                                        name  = summary
                                EXCEPT assignee ).

Mit BASE werden Initialwerte vorgegebenen, siehe VALUE-Operator. Damit werden bei Strukturen die Werte vorgefüllt oder bei internen Tabellen zusätzliche Zeilen an den Anfgang gesetzt.

CORRESPONDING mit Lookup Tabelle

Diese Variante führt ein Lookup auf eine andere interne Tabelle durch. Das entspricht einem LEFT OUTER JOIN mit einer :1 Kardinalität. Wie beim FILTER-Operator funktioniert dies nur dann, wenn der Tabellentyp und die Schlüsseldefinition zu der Join-Bedingung in der USING-Klausel passen.

DATA Lookup TYPE HASHED TABLE OF I_CountryText WITH UNIQUE KEY Country.
DATA(Original) = VALUE tt_demo( ( Country = 'DE' )
                                ( Country = 'US' ) ).
SELECT * FROM I_CountryText WHERE LANGUAGE = 'D' INTO TABLE @Lookup.

DATA(Result) = CORRESPONDING tt_demo( Original FROM Lookup
                                      USING Country        = Country
                                      MAPPING country_text = CountryName  ).

Prädikativer Methodenaufruf

Hinter dem sperrigen Begriff Prädikativer Methodenaufruf verbirgt sich ein einfaches Konzept, das in (fast) allen anderen Programmiersprachen Standard ist: Ein Methodenaufruf als Prädikat, z.B. direkt in einer IF Anweisung. Da in ABAP die Werte TRUE und FALSE nicht eindeutig festgelegt sind, gilt die folgende Definition: Wenn der Rückgabewert einer funktionalen Methode (d.h. mit RETURNING ) initial ist, ist das Prädikat logisch FALSE, ansonsten TRUE. Es entspricht also IF Methode( ) IS NOT INITIAL.

Modernes ABAP

IF isRelevant( ).
  ...
ENDIF.

Klassisches ABAP

IF isRelevant( ) EQ abap_true.
  ...
ENDIF.

Zugriff auf Tabellenzeilen mit Tabellenausdrücken

Auch wenn der Name etwas anderes vermuten lässt, so liefern uns Tabellenausdrücke Zeilen einer Tabelle. In SQL würde man sie daher Zeilenausdrücke nennen.

Sie sind keine Kopien der Zeile, sondern die Zeile in der Tabelle. Deshalb verändern Schreiboperationen auch die Tabelle. Sie ersetzen also READ ... INTO und READ ... ASSIGNING Anweisungen.

Aufbau von Tabellenausdrücken: <Tabelle>[ <Zeilenspezifikation> ]

Die Tabelle kann dabei eine beliebige interne Tabelle sein. Die Zeilenspezifikation steht in eckigen Klammern und kann auf verschiedene Arten erfolgen: Über die Angabe der Zeilennummer bei Standardtabellen, eines freien Schlüssels oder des Tabellenschlüssels.

Modernes ABAP

* Kopie der ersten Zeile
DATA(row) = Users[ 1 ].

* Ändern des Vornamens 1. Zeile
Users[ 1 ]-firstname = 'Edgar'.


* Ändern von Peters Nachname
Users[ firstname = 'Peter'
          ]-lastname = 'Pan'.

Klassisches ABAP

DATA row LIKE LINE OF Users.
READ TABLE Users INTO row INDEX 1.

FIELD-SYMBOLS <row> LIKE LINE OF Users.
READ TABLE Users ASSIGNING <row> INDEX 1.
<row>-firstname = 'Edgar'.

FIELD-SYMBOLS <row>  LIKE LINE OF Users.
READ TABLE Users ASSIGNING <row>
           WITH firstname = 'Peter'.
<row>-lastname = 'Pan'.

Ein Zugriff auf nicht existierende Zeilen erzeugt eine Ausnahme CX_SY_ITAB_LINE_NOT_FOUND.

Modernes ABAP

* Ändern von Peters Nachname
TRY.
  Users[ firstname = 'Peter'
               ]-lastname ='Pan'.
CATCH CX_SY_ITAB_LINE_NOT_FOUND.

ENDTRY.

Klassisches ABAP

* Ändern von Peters Nachname
FIELD-SYMBOLS <row>  LIKE LINE OF Users.
READ TABLE Users ASSIGNING <row>
           WITH firstname = 'Peter'.
IF sy-subrc = 0.
  <row>-lastname = 'Pan'.
ENDIF.

Bei lesendem Zugriff kann alternativ der VALUE-Operator verwendet werden, um einen Defaultwert zu erzeugen oder der Zugriff kann als OPTIONAL markiert sein.

...VALUE #( <Tabelle>[<Zeilenspezifikation>]
             DEFAULT <Alternativer Wert> | OPTIONAL )

Funktionen für interne Tabellen

Die drei Funktionen line_exists( <Tabellenausdruck> ), lines( <InterneTabelle> ) sowie line_index( <Tabellenausdruck> ) erleichtern die Arbeit mit internen Tabellen erheblich und sparen jeweils eine Hilfsvariable ein. Sie können direkt als Ausdruck verwendet werden.

Modernes ABAP

"Existiert ein User mit Vornamen Heidi?
IF line_exists( 
         Users[ Firstname = 'Heidi' ] ).
   ...
   
   
"Gibt es mehr als 10 Zeilen in Users?
IF lines( Users ) > 10.
   ...


"Welchen Index hat die Zeile mit Heidi?
DATA(HeidiIndex) = line_index( 
         Users[ Firstname = 'Heidi' ] ).
		 

Klassisches ABAP

"Existiert ein User mit Vornamen Heidi?
READ TABLE Users TRANSPORTING NO FIELDS 
           WITH KEY Firstname = 'Heidi'.
IF sy-subrc = 0.
   ...

"Gibt es mehr als 10 Zeilen in Users?
DESCRIBE TABLE Users LINES DATA(LineCount).
IF LineCount > 10.
   ...

"Welchen Index hat die Zeile mit Heidi?
READ TABLE Users TRANSPORTING NO FIELDS 
           WITH KEY Firstname = 'Heidi'.
DATA(HeidiIndex) = sy-tabix.

Neue Gruppenstufenverarbeitung

Die klassische Gruppenstufenverarbeitung mit AT ... ist abhängig von der Sortierung der Daten und der Reihenfolge der Spalten. Beides muss exakt passen. Damit ist sie fehleranfällig und manchmal kaum nutzbar. Die neue Gruppenstufenverarbeitung löst dieses Problem durch verschachtelte LOOP-Schleifen.

Modernes ABAP

LOOP AT PlantMats
     INTO DATA(Grouping)
     GROUP BY ( Plant = Grouping-Plant )
     INTO DATA(Grp).
" A
  LOOP AT GROUP Grp
          INTO DATA(PlantMat).
" B
  ENDLOOP.
" C
ENDLOOP.

Klassisches ABAP

LOOP AT PlantMats
     INTO DATA(PlantMat).
  AT NEW Plant.
" A
  ENDAT.
" B
  AT END OF Plant.
" C
  ENDAT.

ENDLOOP.