Hier habe ich einiger meiner Gedanken zusammengestellt um die Codequalität in ABAP zu steigern.
Diese sind mir im Laufe einer großen Qualitätsverbesserungmaßnahme eingefallen.
Wer Anmerkungen zu diesem Thema hat, kann den Beitrag gerne kommentieren.
Wir beschränken hier uns auf das Thema Design und Dokumentation der Prozeduren (Funktionsbausteine, Methoden).
Was hat eine Änderung für Seiteneffekte?
Wenn wir die Form oder den Inhalt der Prozedurschnittstelle ändern, gibt es 2 Kategorien:
Major-Releases:
Hier funktioniert der bestehende Source-Code, der die Prozedur aufruft, nicht mehr.
Dazu wird z.B. folgendes an einer Prozedur geändert:
- ein nicht-optionaler Importing- oder Changing-Parameter wird hinzugefügt
- ein Parameter wird geändert (Typ, Name)
- ein Parameter wird gelöscht
- ein optionaler Parameter ist nicht mehr optional
- eine Ausnahme wird hinzugefügt oder geändert
- eine Ausnahme wird gelöscht
- Umstellung von Wert-Übergabe auf Referenz-Übergabe oder umgekehrt eines Exporting- oder Changing-Parameter
Hier muss immer mit dem Verwendungsnachweis aller Source-Code gesucht werden, der die Prozedur aufruft, und entsprechend angepasst werden.
Wird nur die Form der Schnittstelle geändert, kommt es zu einem Syntaxfehler, der relativ einfach erkannt und behoben werden kann.
Wird der Inhalt einer Schnittstelle geändert wird dies nicht unbedingt als Syntaxfehler erkannt.
Trotzdem müssen alle Aufrufer der Prozedur kontrolliert werden.
Bsp:: in der alten Version übergebt eine Prozedur den Bestand als Summe pro Material und Werk im Exporting-Parameter.
Jetzt ändern wir die Prozedur und der Bestand wird als Summe pro Material, Werk und Charge übergeben.
Wir haben jetzt einen Aufrufer, der den Bestand weiterhin auf Material/Werk Ebene liest mit der Anweisung
Code: Alles auswählen.
READ TABLE bestand ... WITH KEY werks = werk matnr = matnr.
Dieser liest jetzt nur noch den Bestand der 1.Charge statt die Summe pro Material und Werk.
Minor-Release
Hier wird nur eine Erweiterung erstellt. Bestehender Source-Code funktioniert weiterhin.
Dazu wird z.B. folgendes an der Prozedur geändert:
- ein optionaler Importing- oder Changing-Parameter wird hinzugefügt
- ein Exporting Parameter wird hinzugefügt
- Umstellung von Wert-Übergabe auf Referenz-Übergabe eines Importing-Parameters
Also egal wie die Prozedur bisher aufgerufen wird, dies soll nach der Änderung unverändert funktionieren.
Einheitliche Schlüsselwörter in den Kommentare (eigene Pragmas)
Hiermit können Kommentare standardisiert werden, um alle wichtigen Informationen zu enthalten.
Diese haben aber keine Funktion in der Syntaxprüfung.
Folgende Kommentare haben sich als sinnvoll herausgestellt:
- @DEPRECATED: Markiert die Prozedur als obsulet. In Neuentwicklungen sollte dies nicht mehr verwendet werden.
- @ASSERT_CONDITION: Hinweis auf eine assert-Bedingung. (z.B. Methode cl_gui_alv_grid=>set_ready_for_input Hier wird davon ausgegangen, dass die Methode im Online-Modus gerufen wird und nicht im Druck-Modus).
- @DYNAMIC_CALLED PGMID= OBJECT= OBJ_NAME= Prozedur wird dynamisch gerufen. PGMID, OBJECT, OBJ_NAME enthält den Objektkatalog-Eintrag der Aufrufer der Prozedur. Dies kann auch eine Customizing-Tabelle sein. (z.B. im Modul PPPI die Prozessmeldungen. Hier werden Funktionsbausteine abhängig von der Customizing-Tabelle tc51 gerufen.
Die Funktionsbausteine, die die Prozessmeldungen verarbeiten, würde ich mit folgendem Pragme versehen @DYNAMIC_CALLED PGMID=R3TR OBJECT=TABL OBJ_NAME=TC51).
- @PREPERATION vor Aufruf müssen folgende Vorbereitungen getroffen werden (z.B. Meldungen sammeln starten mit dem Funktionsbaustein MESSAGES_INITIALIZE)
- @CONSTRUCTOR in einer Funktionengruppe, der Funktionsbaustein, der die selbe Funktion wie ein Constructor einer Klasse übernimmt (z.B. Funktionengruppe SMSG Funktionsbaustein MESSAGES_INITIALIZE). Wie in den Beiträgen erwähnt, sollte dies nicht mit einem Klassenkonstruktor verwechselt werden. Bei Klassen entfällt dieses Schlüsselwort sowieso.
- @USER_PARAMETER= die Verarbeitung ist abhängig vom einem Parameter im Benutzerstamm (z.B. Buchen eines Materialbeleges mit Funktionsbaustein BAPI_GOODSMVT_CREATE. Hier ist das Drucken eines Warenbegleitscheines abhängig vom Benutzerparameter NDR)
Dynamische Prozeduraufrufe
Diese werden im Verwendungsnachweis nicht gefunden. Dazu können die Aufrufer der Prozedur direkt im Kommentar mit dem Schlüsselwort @DYNAMIC_CALLED erwähnt werden oder im rufenden Source-Code.
Im rufenden Source-Code würde dies wie folgt aussehen:
Code: Alles auswählen.
* call a function module depending on the basis release
data name_fmodule type tfdir-funcname.
CONCATENATE 'ZSAMPLE_READ' sy-saprl INTO name_fmodule.
* we use Release 740, 750
if 1 = 0.
" called in release 740
call function 'ZSAMPLE_READ740'.
" called in release 750
call function 'ZSAMPLE_READE750'.
endif.
call function name_fmodule.
Somit findet der Verwendungsnachweis die Funktionsbausteine ZSAMPLE_READ740, ZSAMPLE_READ750 wieder.
Dokumentation
Neben der Funktionsbeschreibung sollte bei Strukturen eigentlich immer dokumentiert sein, welche Felder vorher gefüllt werden sollen oder verwendet/verarbeitet werden (Voraussetzung: nur ein Teil der Struktur wird benötigt).
Bei BAPIs oder Funktionsbausteinen, die Fehlermeldungen in einer Tabelle übergeben, kann es von Vorteil sein, wenn bereits alle Fehlermeldungen mit der Ursache in der Schnittstelle dokumentiert sind.
Somit wird die Fehleranalyse deutlich vereinfacht.
Stolperfallen
- optionale Parameter können leicht vergessen werden. Daher ist es unter Umständen sinnvoller mehrere Prozeduren zu schreiben mit unterschiedlichen Schnittstellen ohne optionale Parameter. Allerdings muss hier auch abgewägt werden, ob die selbe Funktion dadurch mehrmals implementiert wird. Dadurch wächst später der Aufwand den zu Code zu pflegen.
- Dies sollte nur noch in Ausnahmefällen verwendet werden. Bei größeren Projekten verliert man hier schnell den Überblick. Bei einer späteren Wartung kann es schnell passieren, das eine Memory-ID ungewollt überschrieben wird.
Ausnahmekonzept
Ausnahmen sollten eigentlich dazu dienen, das Programm abzubrechen bevor ein unkontrollierter Zustand entsteht und z.B. ein falscher Beleg gebucht wird.
Diese können in 3 Kategorien unterteilt werden:
- Ausnahmen durch Programmierfehler (bei einer Wartung/Erweiterung). Sollten immer einen Laufzeitfehler verursachen.
- Ausnahmen durch falsche Werte in der Datenbank. Hier kann mit einer Fehlermeldung reagiert werden.
- Ausnahmen durch falsche Eingaben des Benutzers. Kann nur mit einer Meldung reagiert werden.
Mit folgenden Ausnahmen würde ich auf diese Kategorien reagieren
- Programmierfehler: Klassenbasierte Ausnahme (vom Typ cx_dynamic_check), ASSERT-Bedingung, klassische Ausnahme mit Anweisung RAISE
- falsche Werte in der Datenbank: Klassenbasierte Ausnahme (vom Typ cx_static_check) oder klassische Ausnahme mit MESSAGE RAISING
- falsche Benutzereingaben: Klassenbasierte Ausnahme (vom Typ cx_static_check) oder klassische Ausnahme mit MESSAGE RAISING
Die Anweisung MESSAGE wird nur noch in der GUI-Logik verwendet, um GUI und Anwendungslogik klar zu trennen,
In der Schnittstelle sollten alle Ausnahmen propagiert werden, die aufgrund fehlerhafter Schnittstellendaten entstehen.
ASSERT Anweisungen können alternativ verwendet werden, wenn fehlerhafte Schnittstellendaten oder Klassenattribute aufgrund der Wartung/Erweiterung eines Programmes entstehen (z.B. Übergabe eines Zeitraumes, ASSERT_CONDITION Zeitraumende >= Zeitraumanfang). Die ASSERT Anweisungen würde ich dann aber in der Schnittstelle erwähnen.