AMDP Coding Guidelines
Veröffentlicht am 12. Juli 2022 von | SQLScript | AMDP | BW/4HANA |
Diese AMDP Programmierrichtlinien sind ein Vorschlag für ABAP und SAP BW Projekte. Sie sind ursprünglich für den Anwendungsfall von AMDP Transformationsroutinen im SAP BW/4HANA geschrieben worden. Sie können sie gerne kopieren, anpassen und in Ihren eigenen Projekten nutzen, solange dieser Artikel als Quelle verlinkt ist.
Call by Reference - Ein Hinweis zur Nutzung
Dieses Dokument wird im Laufe der Zeit stets aktualisiert: Es wird an neue technische Entwicklungen angepasst, Fehler werden korrigiert und Fehlendes wird ergänzt. Darum empfehle ich Ihnen, dieses Dokument nicht zu kopieren sondern zu verlinken. Damit haben Sie stets den neusten Stand. Ein Interessanter Artikel zu diesem Thema hat Jelena Perfiljeva geschrieben: Are Your ABAP Guidelines Misguided?
Vorwort
Entwicklungsrichtlinien haben mehrere Ziele. Unter anderem:
- Gute Les- und Wartbarkeit des Quellcode
- Einheitlicher Quellcode
- Vermeidung von Fehlerquellen
- Vermeidung von langer Laufzeit
Diese Guidelines ersetzen nicht eine fundierte Ausbildung der Entwickler. Im Umkehrschluss enthalten sie nicht alle möglichen Fallstricke, über die man stolpern kann. Und auch unter strengster Beachtung aller Richtlinien können Fehler und unsauberer Code geschrieben werden. Jeder Entwickler ist dafür veranwortlich, dass sein Quellcode über diese Richtlinien hinaus professionellen Ansprüchen genügt.
Unsauberer Code ist nicht fertig
Der Sinn dieser Guidelines ist sind Minimalanforderungen an die Qualität und Einheitlichkeit. Sie sollten als Teil der Definition of Done für AMDP Transformationsroutinen betrachtet werden. Das bedeutet: Wenn diese Guidelines (noch) nicht vollständig umgesetzt sind, dann ist es der Code auch nicht fertig und darf nicht transportiert werden. Dies gilt auch dann, wenn der Code schon das gewünschte Ergebnis liefert.
Ausnahmen
Natürlich gibt es Situationen, in denen man aus technischen Gründen von den Guidelines abweichen muss. Das sollte man aber
- Nicht alleine entscheiden, sondern immer noch einen kompetenten Kollegen mit einbeziehen. Häufig fehlen einem einfach die guten Ideen.
- Auch explizit im Code in einem Kommentar hinschreiben, warum das so gemacht wurde.
Lesbarkeit des Codes
In sauberem Code können sich keine Fehler verstecken. - Sie können zwar vorkommen, sind dann aber schnell zu entdecken. Entsprechend gilt:
Lesbarkeit vor Optimierung - premature optimization is the root of all evil
There is no doubt that the grail of efficiency leads to abuse. Programmers waste enormous amounts of time thinking about, or worrying about, the speed of noncritical parts of their programs, and these attempts at efficiency actually have a strong negative impact when debugging and maintenance are considered. We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. (Donald E. Knuth, 1974 ! )
Gut strukturierter und gegliederter SQLScript Quellcode, der die im Folgenden beschriebenen Punkte beachtet, kann normalerweise gut optimiert und schnell von der Datenbank ausgeführt werden. Vorauseilende Optimierung, die über die unten genannten Punkte hinaus geht, wird nicht vorgenommen. Bevor Performanceoptimierungen vorgenommen werden, muss auch tatsächlich ein Probleme vorliegen und eine gründliche Laufzeitanalyse durchgeführt worden sein.
Namensgebung
Einige wichtige Punkte sind dazu im Folgenden aufgelistet. Eine umfangreichere und ausführlichere Diskussion des Themas mit vielen Beispielen und guten Gründen finden Sie im Buch Clean Code von Robert C. Martin.
- Alle Bezeichner im Code oder in der Datenbank sind in englischer Sprache.
- Die Namen sollen aussagekräftig sein und den Inhalt beschreiben.
- Tabellen und Tabellenvariablen enden mit einem Plural-S. Beispiel: Materials oder SalesOrderItems
- Nummerierungen oder Allgemeinplätze sind zu vermeiden.
- Für jedes Konzept soll exakt ein Name festgelegt sein. Negativbeispiel: Die Begriffe
Article
,Product
undMaterial
werden für das Gleiche verwendet.
Die Notation von Bezeichner
- Die spezielle Notation mit Gänsefüsschen soll vermieden werden. Sie wird nur dort verwendet, wo die einfache Notation nicht verwendet werden kann. Insbesondere bei den Namensraumpräfixen
/BIC/
und/BI0/
ist das der Fall. - Alle Bezeichner in der einfachen Notation (=ohne Gänsefüsschen) werden intern in Großbuchstaben umgewandelt. Also kann man die Groß- /Kleinschreibung für die bessere Lesbarkeit von Wörtern verwenden, z.B. SalesOrderItems liest sich besser als salesorderitems.
Ungarische Notation und Präfixes
Im SQLScript hat die ungarische Notation keinen Vorteil. Die Unterscheidung zwischen Tabellenvariablen und skalaren Variablen erfolgt eindeutig anhand der Position im Quelltext. Die Abgrenzung zwischen Tabellenvariablen und DB-Tabellen ist ebenso eindeutig.
Einrückung und Formattierung
Die Lesbarkeit des Quellcodes ist für eine gute Wartbarkeit und zur Identifikation von Fehlern sehr wichtig. Einrückungen mit Whitespace erhöhen diese enorm. Die folgenden Regeln sind grundsätzlich anzuwenden:
- Für die Feldliste
- Jedes in der Feldliste ist in einer eigenen Zeile
- Die Felder sind sauber in einer Linie ausgerichtet
- Bei verschachtelten SQL-Funktionen werden die Parameter
- jeder in einer eigenen Zeile geschrieben
- untereinander eingerückt
- Vor den Klauseln und Logischen Operatoren (
FROM
,WHERE
,GROUP BY
,ORDER BY
,ON
,AND
,OR
etc. ) kommt ein Zeilenumbruch. - Zwischen Anweisungen ist stets eine Leerzeile einzufügen
- Blöcke werden eingerückt
Beispiel
Sauber formatiert
CONCAT(
CONCAT(
LEFT(
ABAP_UPPER( firstname)
, 1)
, '. ')
, INITCAP( lastname )) AS Name,
Ohne Zeilenumbrüche und Einrückung
CONCAT( CONCAT( LEFT( ABAP_UPPER( firstname)
, 1) , '. ') , INITCAP( lastname )) AS Name,
Alias Namen und Korrelationsnamen
Für die Vergaben von Alias Namen von Spalten und für die Umbenennung von Tabellenausdrücken mit Korrelationsnamen ist das Schlüsselwort AS
Pflicht. Es erhöht die Lesbarkeit erheblich.
- Spalten-Aliasse sollen nur verwendet werden, wenn sie notwendig sind.
- Korrelationsnamen sollen immer vergeben werden, wenn mehr als eine Tabelle involviert ist. D.h. bei JOINs oder bei korrellierten Unterabfragen.
Schlüsselwörter vollständig hinschreiben
Schlüsselwörter sind immer in der vollständigen Form hinzuschreiben, auch wenn sie optional sind oder eine Kurform existiert. Beispiele:
CROSS JOIN
statt,
AS
INNER JOIN
stattJOIN
LEFT OUTER JOIN
stattLEFT JOIN
Lange Feldlisten vermeiden
Wenn man in jedem SELECT
alle Felder hinschreibt, dann wird der Quellcode episch lang. Statt dessen nehmen wir immer alle Felder aus dem vorherigen Schritt mit Stern *
und fügen die neuen Spalten hinzu.
intab_with_colors = SELECT it.*,
IFNULL(ma.color, '') AS color
FROM :intab as it
LEFT OUTER JOIN mara AS ma
ON ma.matnr = it.matnr;
Warnung: Das gilt nicht im ABAP. Hier sollte ein SELECT *
vermieden werden.
Klammerung
Sobald in Ausdrücken mehr als ein Operator im Spiel ist, müssen Klammern gesetzt werden. Das kostet keine Laufzeit erhöht die Lesbarkeit erheblich. Beispiel:
Was hat der Entwickler hier vor gehabt?
WHERE doctype = 'Z001'
AND itemcat = 'A001'
OR itemcat = 'B001'
So hat es die Datenbank verstanden
WHERE ( doctype = 'Z001'
AND itemcat = 'A001' )
OR itemcat = 'B001'
Und das wollte der Entwickler erreichen
WHERE doctype = 'Z001'
AND ( itemcat = 'A001'
OR itemcat = 'B001 )'
Kommentare
Grundsätzlich soll in produktivem Code immer der Zeilenendkommentar, eingeleitet durch den doppelten Bindestrich --
verwendet werden. Blockkommentare mit /*...*/
hingegen sind dem Arbeiten in der SQL-Konsole vorbehalten. ABAP Zeilenkommentare (der Stern *
am Zeilenanfang) sind verboten, auch wenn sie in AMDP funktionieren. Der Grund ist, dass sich der Code nicht mehr 1:1 in der SQL-Konsole ausführen lässt.
- Einleitungskommentare beschreiben die Struktur der Prozedur als Ganzes und in welchem Kontext sie steht.
- Gliederungskommentare erleichtern die Navigation im Code. Sie unterteilen diesen in Abschnitte.
- Erklärende Kommentare beschreiben, warum etwas gemacht wurde oder erläutern komplexe Logik, beispielsweise in regulären Ausdrücken. Sie unterstützen den Lesefluss.
- Kommentare, die das offensichtliche beschreiben, sind überflüssig.
- Kopfkommentare die die Informationen aus dem Objektkatalogeintrag und der Versionshistorie wiederholen sind ebenfalls überflüssig.
Auskommentierter Code ist nur während der Entwicklungsphase akzeptabel und hat in produktivem Code nichts zu suchen.
Dos and Don'ts
Die HANA Datenbank ist dafür optimiert, dass sie deklarativen Code ausführt. Imperativer Code läuft erheblich langsamer. In erster Linie, weil eine Parallelisierung verhindert wird. Darüber hinaus verhinder Imperativer Code eine vollständige Optimierung. Deshalb gilt:
Imperativer Code ist verboten
Ausnahmen bedürfen einer gründlichen Prüfung.
Damit bleiben nur wenigen Anweisungen übrig, die im AMDP verwendet werde dürfen:
SELECT
Anweisungen- Zuweisung von Tabellenvariablen
- Aufruf von deklarativen Prozeduren
Dynamisches SQL ist zu vermeiden
Dynamisches SQL lässt sich nicht gut von der Datenbank optimieren. Je nach Anwendungsfall gibt es auch gute Alternativen:
- Auslagern von DB-Tabellenzugriffen, siehe Blog-Artikel Wiederverwendung von Geschäftslogik in AMDP Transformationsroutinen
- Dynamisches Filtern mit der APPLY_FILTER Funktion.
Darüber hinaus ist die Komplexität von dynamischem Code stets erheblich höher und es fällt schwer ihn nachzuvollziehen. Ausserdem ermöglicht er Code Injection Angriffe.
Abfragen in kleine Schritte aufteilen
Komplexität in einer SELECT
-Abfrage entsteht immer dann, wenn mehr als eine Sache gleichzeitig gemacht wird, beispielsweise JOIN
und Aggregation oder der mehrere JOIN
-Operationen auf einmal. Das erschwert die Lesbarkeit und die Fehlersuche.
Statt dessen sollen die Abfragen in mehrere kleine Schritte, die durch Tabellenvariablen verbunden sind, aufgeteilt werden.
Beispiel
Ein Schritt
Die Mischung von JOIN
und GROUP BY
erschwert die Lesbarkeit. Der kürzere Code täuscht Einfacheit vor.
DO BEGIN
SELECT u.firstname,
u.lastname,
COUNT(*) AS task_cnt,
MAX(due_date) AS max_dd
FROM users AS u
INNER JOIN tasks AS t
ON u.id = t.assignee
GROUP BY u.firstname,
u.lastname;
END;
Zwei Schritte
Die Zerlegung in zwei triviale Schritte erhöht die Lesbarkeit. Sinnvollen Namen für die Tabellenvariablen unterstützen dabei.
DO BEGIN
tasks_per_user = SELECT u.firstname,
u.lastname,
t.id,
t.due_date
FROM users AS u
INNER JOIN tasks AS t
ON u.id = t.assignee;
SELECT firstname,
lastname,
COUNT(id) AS task_cnt,
MAX(due_date) AS max_dd
FROM :tasks_per_user
GROUP BY firstname,
lastname;
END;
Unterabfragen
Unterabfragen sind nur in der WHERE
Klausel erlaubt. Insbesondere in den Prädikaten EXISTS
und IN
, so wie als Vergleichswert Vergleichsprädikaten.
In der FROM
-Klausel sind Unterabfrfagen verboten. Statt dessen sind Tabellenvariaben zu nutzen. Grund hierfür ist die geringere Komplexität und bessere Lesbarkeit.
In der Feldliste sind skalare Unterabfragen ebenfalls nicht erlaubt. Statt dessen müssen die Daten per JOIN
hinzugefügt werden. Gegebenenfalls müssen diese vorab in einer Tabellenvariablen aufbereitet werden. Grund hierfür ist die Performance.
NULL-Werte sofort abfangen
Der Code ist so zu gestalten, dass keie NULL
-Werte in Tabellenvariablen stehen. In den SAP BW Tabellen können wir uns darauf verlassen, dass NULL nicht in der Datenbank steht. Trotzdem kann NULL
innerhalb unserer Routinen entstehen, z.B. durch OUTER JOIN
oder CASE
Ausdrücke. Mit den Funktionen
IFNULL()
undCOALESCE()
wirdNULL
durch einen geeigneten Wert ersetzt.
Mit dem Prädikat IS NULL
bzw. IS NOT NULL
können Datensätze mit NULL
-Wert herausgefiltert werden.
Alle CASE
Ausdrücke müssen einen ELSE
-Zweig haben um den NULL
-Wert zu verhindern.
Session Variablen
Die vordefinierten Session Variablen dürfen nur lesend verwendet werden. Eine Veränderung kann die Verarbeitung an anderen Stellen der Programmlogik beeinflussen.
Selbst definierte Session Variablen sollen nicht verwendet werden.
Implizites Casting vermeiden
Die HANA Datenbank führt implizit Typkonvertierungen aus, wo das notwendig ist. Das erschwert die Lesbarkeit und erhöht die Fehleranfälligkeit. Darum soll der Entwickler immer explizite Konvertierungen verwenden und wo möglich bei der Konvertierung der korrekte Typ verwendet werden.
Fehlende Typkonvertierungen können Anfänger auch auf die falsche Fährte locken
Beispiel falsche Rückschlüsse aus fehlender Typkonvertierung
SELECT LEFT(current_date, 4) AS year --liefert das aktuelle Jahr, fast wie in ABAP
FROM dummy;
Die Zeichenkettenfunktion LEFT
macht einen glauben, dass das Datum intern eine Zeichenkette ist. Mit Explain können wir aber herausfinden, dass die Datenbank vorher einen TO_CHAR
macht. Und falls ein Spaßvogel vorher das Default Format von DATE
geändert hat, dann liefert die obigen Anweisung vielleicht '12/0
'.
Typgerechte Literale verwenden
Konstante Werte werden als Literal im Quellcode geschrieben. Diese sollten immer vom richtigen Datentyp sein. Die wichtigsten Literale:
- Zeichenketten werden in Hochkomma geschrieben:
'Zeichenkette'
- Unicode-Zeichenketten mit führendem, großen N:
N'Jörg'
- Numerische Daten werden ohne Hochmkoma direkt hingeschrieben. Dezimaltrennzeichen ist der Punkt.
3.1415
- Zeit-Literale werden als Zeichenkette im Standardformat mit vorangestelltem Datentyp geschrieben:
DATE'2022-07-12'
Beispiel mehrdeutiger Code wegen unterschiedlichem Datentyp
Anmerkung: Die HANA Datenbank führt das nach eindeutigen Regeln aus, mehrdeutig ist das nur für den menschlichen Leser.
WHERE datum < '20090119';
Da SQL die Datentypen DATE
und VARCHAR
nicht vergleichen kann, findet zunächst eine Konvertierung statt. Aber welche?
- das Datum in eine Zeichenkette ('
2009-01-19
') oder - die Zeichenkette in ein Datum
So ist es eindeutig:
WHERE datum < date'2009-01-19';
Damit wird der Vergleich leicht erkennbar zwischen zwei DATE
Feldern durchgeführt.
Literale mit einem Leerzeichen
Auf Grund einer Besonderheit im AMDP verhalten sich Literale mit nur genau einem Leerzeichen anders als erwartet, siehe dieser Blog Artikel Der ABAPVARCHARMODE: Leerzeichen und leere Zeichenketten in ABAP und SQLScript. Statt dessen ist die SQL-Funktion CHAR(32)
zu verwenden:
SELECT firstname || CHAR(32) || lastname AS Name
FROM users;
Datenvolumen früh einschränken
Alle statisch bekannten Filterbedingungen werden direkt beim Datenbankzugriff angewendet.
Skalare UDFs vermeiden
Skalare User Defined Functions (UDF) sind potenziell langsam in der Ausführung, siehe dieser Blog Artikel. Darum sollten sie nicht verwendet werden.
Keine Konvertierungen und SQL-Funktionen in JOIN-Bedingungen
Ein Equi-Join wird von der Datenbank am schnellsten verarbeitet. Jede Konvertierung verringert die Ausführungsgeschwindigkeit. Ungünstig sind
- Unterschidliche Datenlängen
- Unterschiedliche Datentypen, die Explizit oder Implizit konvertiert werden müssen
- SQL-Funktionen wie z.B.
LPAD()
oderLTRIM()
In diesen Fällen soll geprüft werden, ob die entsprechende Spalte in einem für den JOIN
optimierten Format persistiert werden kann.
UNION ALL bevorzugen
Falls möglich ist die Mengenoperation UNION
zu vermeiden und statt dessen UNION ALL
zu verwenden. Ersterer entfernt Duplikate aus der Ergebnismenge. Das ist häufig überflüssig, da
- Keine Duplikate in den Daten vorkommen, z.B. weil ein Feld
RECORD
oderTIMESTAMP
stets unterschidlich ist oder weil die Daten entsprechend selektiert wurden - Es egal ist, ob in der Ergebnismenge Duplikate vorkommen, z.B. in der
OUTTAB
einer Transformationsroutine. Hier überschreiben sich die Duplikate beim Aktivieren der Daten.
Window Functions vorsichtig einsetzen
Window Functions (WF) verschlechtern die Performance aus mehreren Gründen:
- Die Optimierung wird durch WF teilweise blockiert. z.B. können Filterbedingungen nicht nach unten verschoben werden.
- Die WF werden in der Row-Engine ausgeführt. Die Daten müssen also zwischen den Engines hin und her kopiert werden.
Für viele Punkte sind Window Functions aber die beste bzw. einzige Lösung. Hier muss man sicherstellen, dass die Anzahl der Datensätze, auf die sie angewendet wird möglichst klein ist. Also alle Filterungen vor der WF stattfinden.
Falls die WF auf eine sehr große Datenbanktabelle bezieht, kann in Transformationsroutinen ein voriger INNER JOIN
mit der INTAB
das Datenvolumen wirkungsvoll einschränken.
Datenbankhints
Datenbankhints werden nicht verwendet.
Falls die SAP einen Hint als Reaktion auf eine Meldung empfliehlt, muss nach jedem Update geprüft werden, ob dieser noch notwendig bzw. hilfreich ist.
Speziell für AMDP Transformationsroutinen
Die folgenden Themen beziehen sich nur auf AMDP Transformationsroutinen im BW/4HANA oder BW on HANA. Sie haben keine Relevanz für eine allgemeine AMDP und SQLScript Entwicklung.
Maximal eine Routine
Die Logik in einer Transformation sollte möglichst zentral an einer Stelle implementiert werden. Für eine bessere Übersicht soll eine Verteilung auf viele Baustellen vermieden werden. Das bezieht sich nicht nur auf Routinen sondern auch auf Formeln.
Verwendung von DDic-Based CDS Views
Die neuen CDS View Entities (ab ABAP release 7.55) haben ein großen Nachteil in AMDP Routinen. Sie können dort zum Lesen von Daten eingesetzt werden. Aber es ist nicht möglich, den Quellcode dann in die SQL-Konsole zu kopiere und dort auszuführen.
Deswegen soll beim Datenbankzugriff per CDS immer der SQL-View der alten DDic-Basierten CDS Views verwendet werden.
Struktur der Routinen
AMDP Transformationsroutinen werden sinnvoller Weise in mehrere Schritte untergliedert. Bewährt hat sich die folgende Struktur:
- Eingangsprojektionen - Alle in der USING Klausel genannten Tabellen werden ganz am Anfang in eine Tabellenvariable geladen. Dabei gilt:
- Nur die Spalten mitnehmen, die später gebraucht werden
- Notwendige Berechnungen können hier gemacht werden
- Umbenennung von Spalten kann sinnvoll sein, damit später mit einem Stern in der Feldliste gearbeitet werden kann.
- Die Daten werden, soweit möglich, bereits eingeschränkt
- Viele kleine Schritte - In jedem Schritt immer nur einen Aspekt bearbeiten. Die Daten aus dem vorigen Schritt können mit
*
mitgenommen werden. - Ausgangsprojektion - Ganz am Ende kommt eine Ausgangsprojektion. Hier ist keine Logik mehr untergebracht. Es geht nur darum, die Felder in die richtige Reihenfolge für die
OUTTAB
zu bringen.
Zusammenfassung
Diese Guidelines setzen Leitplanken, innerhalb derer man sich sicher bewegen kann. Es beschreibt viele unterschiedliche Aspekte, die bei der AMDP und SQLScript Entwicklung beachtet werden sollen. Es sollte nur aus guten Gründen davon abgewichen werden.
Sie erklären aber nicht die grundlegenden Konzepte hinter der Programmiersprache SQLScript und dem AMDP Framework, das Vorgehen bei der Erstellung von Transformationsroutinen und all die technischen Details, die man kennen sollte, um gute Transformationsroutinen zu schreiben. Dafür ist eine fundierte Ausbildung der Entwickler notwendig.
Feedback erwünscht
Ich freue mich über jedes Feedback und fruchtbare Diskussionen zu diesen Themen. Bitte schicken Sie mir eine E-Mail oder nutzen das Kontaktformular, wenn Sie Anmerkungen dazu haben.