Erste Schritte zu sauberem ABAP
In meinem letzten Blogbeitrag ging es neben den Grundbausteinen der modernen ABAP-Entwicklung auch darum, wie SAP versucht, die ABAP-Entwicklung für das 21. Jahrhundert fit zu machen. Dabei konzentrierte ich mich auf einige praktische Maßnahmen wie automatisierte Komponententests und die automatisierte Änderungsverwaltung. Aber neben der eigentlichen Arbeitsweise ist auch der dabei entstehende Code sehr wichtig. Daher möchte ich an dieser Stelle auf meine Vorstellungen von einem „sauberen ABAP-Code“ näher eingehen.
Das richtungsweisende Buch „Clean Code“ von Robert C. Martin ist inzwischen zu einem Klassiker des Themas avanciert. Es beschreibt Best Practices zum Schreiben von wartungsfreundlichem und gut lesbarem Code, die mittlerweile in der Softwarebranche weite Verbreitung gefunden haben. In letzter Zeit befasst sich auch die ABAP-Community mit der Clean-Code-Philosophie. Die Wichtigkeit des Themas wird allein schon durch die Tatsache unterstrichen, dass SAP einen eigenen ABAP-Styleguide veröffentlicht hat, der vollständig auf diesen Methoden basiert.
Der für eine erfolgreiche Implementierung erforderliche kulturelle Wandel fällt in der ABAP-Welt jedoch noch umfassender aus als anderswo. Dieser Beitrag soll Teams dabei helfen, sauberen ABAP-Code zu schreiben, ohne ihre bisherigen Arbeitsweise völlig auf den Kopf stellen zu müssen.
Die Pfadfinderregel
Code neigt einfach dazu, mit der Zeit im Wildwuchs zu verwahrlosen. Es werden oft schnell ein paar Notkorrekturen hinzugefügt. Aus Zeitgründen wird in Eile kopiert und eingefügt, und Quellcode, der einmal schön war, wird allmählich unlesbar und immer schwerer verständlich.
Diese Vorgehensweise ist weit verbreitet und erschwert das Schreiben von sauberem Code. Da die Verwahrlosung jedoch ein schleichender Prozess ist, können wir sie stoppen, indem wir einen allmählichen Verbesserungsprozess einleiten. Dabei unterstützt uns eine einfache Regel der Pfadfinder: „Hinterlasse einen Ort immer schöner, als du ihn vorgefunden hast.“ Dieselbe Regel kann auch für die (ABAP-)Programmierung gelten: Wenn du eine Methode oder Funktion änderst, muss der Code nach der Änderung sauberer sein als vorher.
Dieser Ansatz sorgt mit minimalem Risiko dafür, dass die gesamte Codebasis im Laufe der Zeit immer sauberer wird, ohne dass in kurzer Zeit viele drastische Änderungen vorgenommen werden müssen.
Modularisierte Methoden mit einfacher Zweckbestimmung
ABAP-Code war in der Vergangenheit dafür berüchtigt, dass Funktionen, Programme und Methoden manchmal Tausende von Zeilen lang waren. Das ist der Stoff, aus dem Katastrophen entstehen. In modernem ABAP-Code sollten Entwickler Klassen und Methoden nur zu Modularisierungszwecken verwenden. Jede Methode sollte „nur genau eine Sache“ erledigen, diese aber dafür auch richtig gut.
Dazu muss die Methode kurz sein (maximal 10 Anweisungen), nur sehr wenige Eingabeparameter und genau einen Ausgabeparameter umfassen und sich auf einer einzelnen Abstraktionsebene befinden. Nehmen wir zum Beispiel die folgende Methode:
METHOD get_user_data. IF username IS INITIAL. RAISE EXCEPTION TYPE zcx_argument_exception. ENDIF. IF username(1) = 'Z'. AUTHORITY-CHECK OBJECT 'Z_USER' ID 'USERNAME' FIELD username. IF sy-subrc <> 0. RAISE EXCEPTION TYPE zcx_no_authorization. ENDIF. CALL FUNCTION 'Z_FETCH_USER' DESTINATION rfc_destination EXPORTING user_data = user_data IMPORTING username = username EXCEPTIONS OTHERS = 1. IF sy-subrc <> 0. RAISE EXCEPTION TYPE zcx_rfc_error. ENDIF. ELSE. SELECT SINGLE * FROM z_users INTO user_data WHERE uname = username. user_data-additional_data = get_additional_data( username ). ENDIF. ENDMETHOD.
Diese Methode ist zwar nicht übermäßig lang, macht aber zu viele Dinge. Sie validiert die Eingabe, bestimmt den Benutzertyp anhand des ersten Zeichens, führt Berechtigungsprüfungen durch, behandelt zwei verschiedene Hauptausführungsfälle, ruft Daten aus der Datenbank ab usw. Besser ist, wenn wir sie in separate Methoden aufteilen und dazu mit der höchsten Abstraktionsebene beginnen:
METHOD get_user_data. validate_input( username ). IF is_special_user( username ) = abap_true. user_data = get_special_user_data( username ). ELSE. user_data = get_normal_user_data( username ). ENDIF. ENDMETHOD.
Natürlich sollten die hier neu eingeführten Methoden nach der gleichen Philosophie implementiert werden, jedoch eine Abstraktionsebene tiefer.
Methodenschnittstellen und -aufrufe bereinigen
Wenn Sie sich an die vorgenannte Regel halten, bleiben die Methodenschnittstellen recht einfach. (Im Idealfall enthalten sie nur wenige Importparameter und einen einzigen Rückgabeparameter.) Auf diese Weise können Sie mit dem Funktionsaufrufstil den Code kompakter gestalten. Anstelle von:
CALL METHOD database->get_entries EXPORTING key = my_key limit = 1000 RECEIVING result = entries.
... verwenden Sie:
entries = database->get_entries( key = my_key limit = 1000 ).
Die CALL METHOD-Anweisung soll generell nur noch für den dynamischen Methodenaufruf verwendet werden. Ebenso sollten die überflüssigen Schlüsselwörter EXPORTING und RECEIVING möglichst weggelassen werden. CHANGING-Parameter sollten sparsam und mit Vorsicht eingesetzt werden, da sie fehleranfällig sind und zu unvorhersehbaren Begleiterscheinungen führen können.
Öffentliche Methoden einer Klasse sollten als Schnittstelle definiert werden, die die Klasse implementiert. (In der Theorie der objektorientierten Programmierung sollte jede Klasse einen durch eine Schnittstelle dargestellten Kontrakt erfüllen.) Dadurch wird der Code lose gekoppelt und hilft beim Schreiben automatisierter Komponententests.
Bedingungen und Kontrollzweige
Bedingungen und Verzweigungen sind zwei der am häufigsten verwendeten Werkzeuge beim Programmieren. Daher ist es wichtig, sie richtig zu beherrschen. Sehr komplexe Bedingungen sind unter Umständen nur schwer lesbar. Möglicherweise ist es sinnvoll, sie in eine separate Variable oder Methode zu extrahieren, um die Absicht und die Bedeutung klar zu machen. Zum Beispiel anstelle von …
IF username IS NOT INITIAL OR ( address IS NOT INITIAL AND city IS NOT INITIAL ) OR ( security_number IS NOT INITIAL ). " ... ENDIF.
… könnten Sie den Code aussagekräftiger machen, indem Sie eine zusätzliche Variable einfügen:
sufficient_data_provided = username IS NOT INITIAL OR ( address IS NOT INITIAL AND city IS NOT INITIAL ) OR ( security_number IS NOT INITIAL ). IF sufficient_data_provided = abap_true. " ... ENDIF.
In ABAP sind die beiden Verzweigungsanweisungen IF und CASE verfügbar. Wenn sich viele Optionen gegenseitig ausschließen sollen, ist es besser, die CASE-Anweisung zu verwenden. Dies ist deutlich übersichtlicher als eine Reihe von IF… ELSEIF-Anweisungen.
Außerdem sollten IF-Anweisungen grundsätzlich nicht zu tief verschachtelt werden, da auch dies sehr schnell unübersichtlich wird. Wenn dies aus irgendeinem Grund erforderlich erscheint, ist Ihre Methode wahrscheinlich zu komplex. Überlegen Sie dann, ob Sie sie nicht aufteilen können.
Deskriptive, aussagekräftige Namen verwenden
„In der Informatik sind nur zwei Dinge wirklich schwierig: Cache-Entwertung und die Benennung von Dingen.“ -- Phil Karlton
In der Vergangenheit war es üblich, im ABAP-Code zumeist kryptische und stark abgekürzte Namen zu verwenden. Dies hat hauptsächlich historische Gründe. Allerdings gibt es in ABAP noch immer strenge Einschränkungen für die Länge von Benennungen. Dass es dadurch schwierig wird, aussagekräftige Namen zu finden, bedeutet jedoch nicht, dass wir es nicht versuchen sollten.
Wir müssen im Blick behalten, dass Programme nicht für Computer geschrieben werden. Sie sind für andere Entwickler geschrieben, die sie lesen und pflegen müssen. (Wer weiß, vielleicht sind Sie in sechs Monaten selbst einmal dieser „andere Entwickler“…) Der Code sollte möglichst in klarem Englisch abgefasst sein. Anstatt beispielsweise eine Variable mit dem Namen WA_T001 zu deklarieren, sollten Sie einen aussagekräftigen Namen wie COMPANY_DATA verwenden.
Für Objekt- und Schnittstellennamen sollten Substantive verwendet werden, für Methodennamen eher Verben. Halten Sie sich an einen Namen pro Konzept. Verwenden Sie beispielsweise nicht die Wörter „update“, „change“ und „modify“, um denselben Vorgang zu beschreiben. Vermeiden Sie nach Möglichkeit Abkürzungen. Dies ist in ABAP aufgrund der oben genannten Einschränkungen manchmal schwierig. Falls Abkürzungen unumgänglich sind, verwenden Sie diese einheitlich.
In ABAP kommt traditionell auch die „ungarische“ Notation zum Einsatz, bei der technische und kontextbezogene Informationen als Präfixe für Variablen codiert werden. Wir alle kennen Namen wie LT_DATA, GV_COMPANY oder MST_STATIC_TABLE. Dies ist ein veraltetes Programmierkonzept, von dem inzwischen abgeraten wird. Es kann jedoch schwierig sein, diese Empfehlungen umzusetzen, wenn für ein Projekt bereits bestimmte Namenskonventionen gelten. Diese sollten dann aus Gründen der Einheitlichkeit befolgt werden, um Missverständnisse zu vermeiden.
Weiterführende Literatur
Die vorgenannten Punkte sind nur die Spitze des Eisbergs. Ich habe versucht, einige leicht umzusetzende Dinge zusammenzustellen. Aber selbstverständlich gibt es noch viele weitere Konzepte. Wenn Sie sich für das Thema interessieren und den Code in Ihrem Team verbessern möchten, empfehle ich dringend, das Buch „Clean Code: Refactoring, Patterns, Testen und Techniken für sauberen Code“ vollständig zu lesen. Darin sind viele dieser Praktiken ausführlicher beschrieben. Weitere Informationen zur ABAP-spezifischen Anwendung von Clean-Code-Konzepten finden Sie im SAP-eigenen Styleguide.