Diesen Artikel habe ich ursprünglich im SAP Community Network veröffentlicht.
Triggerwarnung: Dieser Blog wird wahrscheinlich eine Reihe von Menschen verärgern. Er schlummert schon seit einiger Zeit in meinem Hinterkopf, ursprünglich inspiriert von Ralf Wenzels Artikel Hungarian beginner’s course - a polemic scripture against Hungarian Notation. Unter anderem wäre ich nicht allzu überrascht, kritische Kommentare von Matthew Billingham zu erhalten – aber vielleicht auch nicht. Wie Tolkien es vor langer Zeit ausdrückte: “So many strange things have chanced that to learn the praise of a fair lady under the loving strokes of a Dwarf’s axe will seem no great wonder.” Ich dachte mir, ich könnte es genauso gut versuchen, auch wenn es meine Geburtstagsfeier verderben könnte.
Um es auf den Punkt zu bringen: Ich selbst verwende Präfixe für Variablen und Parameter und ich ermutige die Leute, ein einheitliches Präfixsystem in ihrem gesamten Code zu verwenden. Meiner Erfahrung nach hilft ein sinnvolles Benennungsschema für Variablen bei der Entwicklung und Pflege von ABAP-Code. Ich glaube nicht, dass ich jemanden überzeugen kann, der das nicht akzeptieren will, aber ich glaube, dass es in dieser Diskussion eine Reihe von Argumenten gibt, die zu leicht vergessen oder beiseitegeschoben werden. Mir ist durchaus bewusst, dass dieses Thema garantiert eine lebhafte Diskussion auslösen wird, daher werde ich versuchen, den Ton etwas förmlicher und weniger emotional zu halten als Ralf es in seinem Beitrag getan hat. (Um fair zu sein – seine Trigger-Warnung steht sowohl im Titel als auch in der Einleitung.)
Vielleicht zuallererst: Welches Präfixsystem verwende ich? Es läuft auf eine Kombination aus einem oder zwei Buchstaben, einem Unterstrich und einem sinnvollen Namen für die Variable hinaus. Ich werde mich hier auf die Zusammensetzung des Präfixes konzentrieren, da es in der Diskussion über die „ungarische Notation“ darum geht, ob Sie Ihre Variablen nach den Wörterbuchstrukturen benennen möchten oder lieber einen „englischen“ Namen haben möchten. Dies ist eine andere Diskussion.
Der erste Buchstabe bezeichnet den Ursprung oder den Umfang der Variablen oder des Parameters:
- Klassenmitglieder:
g
= Instanzattributs
= statisches Attribut
- Methoden und (gegebenenfalls) Funktionsmodule:
l
= lokale Variablei
= Importparametere
= Exportparameterc
= Änderungsparameterr
= Rückgabeparameter
- Programme, Funktionsgruppen
g
= globale Variablep
=PARAMETERS
(nur Bericht)
Der zweite Buchstabe unterscheidet zwischen den Datentypen:
- (keine) - elementarer Datentyp
s
= Strukturt
= interne Tabeller
= Referenz
Für die WebDynpro-Codierung verwende ich eine etwas andere Notation – hauptsächlich, weil ich zu faul bin, den generierten Code durchzugehen und alles anzupassen:
v
= elementarer Datentyp (WebDynpro-Codierung, da dies der Standard im generierten Code ist)o
= Objektreferenz (WebDynpro)r
= Datenreferenz
Ausnahmen und Ungereimtheiten, die ich noch nicht überwunden habe:
so_
=SELECT-OPTIONS
co_
= Konstanten- kein Präfix für öffentliche schreibgeschützte Attribute von Persistenzklassen
- kein Präfix für Konstanten in „enum interfaces“, die nur Konstanten enthalten
TABLES
- hallo, Dynpros! - für Strukturen AUSSCHLIESSLICH, NIE für transparente Tabellen, gleicher Name wie die Struktur
So erhalten wir zum Beispiel
gt_foo
in einer Klasse – das wäre ein Instanzattribut, das eine interne Tabelle istls_bar
– eine strukturierte lokale Variableir_baz
– ein importierender Referenzparameter
Für FIELD-SYMBOLS
verwende ich in der Regel dieselbe Notation, sodass Sie in meinem Code Dinge wie <ls_foo>
oder
<lr_bar>
sehen werden. Da ich globale FIELD-SYMBOLS
eher vermeide, könnte ich das l
weglassen, habe mich aber
entschieden, es beizubehalten, um den Regelsatz zu vereinfachen.
Zusammengefasst: Hier gibt es nichts Besonderes zu sehen. Ich behaupte nicht, dieses Präfixsystem erfunden zu haben, sondern habe es nur übernommen und leicht angepasst – obwohl es so allgemein gehalten ist, dass wahrscheinlich Hunderte von Entwicklern auf der ganzen Welt entweder dieses System oder etwas sehr Ähnliches verwenden. Es ist etwas ausführlicher als das von Kai Meder in ABAP Modern Code Conventions, aber ich glaube, dass die Unterschiede ein zusätzliches Zeichen rechtfertigen.
Bevor ich die Vor- und Nachteile im Detail diskutiere, möchte ich auf eine einfache und wichtige Tatsache hinweisen, die in vielen Diskussionen übersehen zu werden scheint: Es gibt kein perfektes Leben. Es gibt nur das Leben. Wir müssen mit dem auskommen, was wir haben, und während wir immer danach streben können (und sollten!), unsere Fähigkeiten zu verfeinern und uns selbst sowie unsere Umwelt zu verbessern, gibt es Mängel, Unzulänglichkeiten und historisch erklärbare, aber dennoch hässliche Notlösungen, mit denen wir uns einfach abfinden müssen. Der Traum von einer Welt ohne globale Variablen, Methoden, die nicht länger sind als (hier Zahl einfügen) Zeilen sind Codezeilen sind, und reiner Objektorientierung mag ein angenehmer Zeitvertreib sein, aber er bringt uns nicht weiter. Die Welt ist da draußen, und sie enthält laaaaaange Methoden (von denen viele mal ziemlich kurz waren und durch jahrelange Wartung durch viele Hände und mangelndes Refactoring immer länger wurden), wahnsinnig komplexe Systeme und eine seltsame Mischung aus objektorientierter und prozeduraler Programmierung, sodass wir uns täglich mit solchen Dingen auseinandersetzen müssen.
Wir müssen uns auch mit einer weiteren wichtigen Tatsache auseinandersetzen: Menschen machen Fehler. Selbst der intelligenteste und erfahrenste Entwickler wird irgendwann einen Fehler produzieren (andere Blog-Autoren und -Moderatoren natürlich ausgenommen). Da eine Namenskonvention für das System keinerlei Nutzen hat, muss sie optimiert werden, um den Menschen vor der Maschine zu unterstützen. Im Idealfall sollte sie dabei helfen, den bereits vorhandenen Code zu verstehen und Codierungsfehler zu vermeiden, wo immer dies möglich ist – oder sie zumindest deutlicher machen. Sehen wir uns also einige häufige Fehler an und wie die oben genannten Benennungskonventionen dazu beitragen, diese zu verhindern oder zumindest zu erkennen.
Beginnen wir mit einer häufigen Kontroverse – warum überhaupt Präfixe für lokale und globale Variablen verwenden? Es
gibt zwei Argumente, die mich dazu bringen, die Verwendung der Präfixe g
und l
zu empfehlen, obwohl viele namhafte
Stimmen gegen ihre Verwendung argumentieren. Zunächst einmal –
shadowing und name
masking. Diese Konzepte gibt
es in vielen Programmiersprachen, und ich habe noch keine reale Anwendung gefunden, die nicht mit einer gehörigen
Portion Sadismus gegenüber den armen Seelen verbunden ist, die den Code anschließend warten müssen. Meiner Erfahrung
nach ist es so, dass überall dort, wo außerhalb einer Programmierklasse tatsächlich geschieht, ist beim Refactoring oder
bei der Wartung des Codes in der Regel etwas schiefgelaufen. Durch das shadowing entstehen besonders unangenehme
Fehler, deren Aufspürung leicht Stunden oder sogar Tage dauern kann. Die Aufteilung des Namensraums in verschiedene
Partitionen für globale und lokale Variablen löst dieses Problem auf elegante Weise – wenn alle Klassenattribute mit g
(oder s
) und alle methodeninternen Variablen mit l
beginnen, wird das shadowing nie zum Problem. Andere Sprachen
lösen oder umgehen das Problem, indem sie Compiler-Warnungen und intelligente IDE-Erweiterungen hinzufügen, um dem
Entwickler zu helfen, konfliktierende Variablen zu erkennen, was nur eine weitere Behelfslösung für
dasselbe Problem ist, und eine, die wir noch nicht haben (als ich das letzte Mal nachgesehen habe, hat selbst das
großartige ATC nicht vor konfliktierenden Variablen gewarnt).
Das zweite Argument für „Bereichspräfixe“, das ich vorstellen möchte, steht im Zusammenhang mit dem Gegenargument
„Variablen sind immer lokal, und wer bestimmt überhaupt, was in einer objektorientierten Anwendung als global bezeichnet
wird?“ Es ist sicherlich nicht einfach, die letztere Frage zu beantworten, aber es hilft, das Problem aus einem anderen
Blickwinkel zu betrachten: Wenn ich den Inhalt dieses Dingsbums da drüben ändere, welchen Umfang (oder welche
Lebensdauer, wenn Sie so wollen) hat diese Änderung dann? Wirke ich mich nur auf den Ausführungskontext der Methode aus,
in der ich mich gerade befinde, und was auch immer ich ändere, wird verworfen, wenn die Methode verarbeitet wird, oder
ändere ich dauerhaft den Status der aktuellen Instanz oder sogar der gesamten Klasse? Man könnte diese Änderungen in
Ermangelung eines besseren Namens als „lokal“ und „global“ bezeichnen. Natürlich lässt sich dies leicht mit einem
einfachen Doppelklick feststellen, aber es ist viel einfacher, Code zu schreiben und zu pflegen, wenn diese
Informationen in den Bezeichner eingebettet sind. Wenn Sie dies konsequent anwenden, insbesondere als
Programmieranfänger, werden Sie feststellen, dass es sich nach einer Weile etwas seltsam anfühlt, etwas mit dem Präfix
g
zu verwenden – Sollte dies wirklich global sein, brauche ich das noch irgendwo anders, kann ich das nicht einfach
zu einer lokalen Variablen machen? – während der Zugriff auf etwas mit dem Präfix s
außerhalb einiger speziell dafür
vorgesehener Factory-Methoden sofort Alarmglocken läuten lässt, wenn man es nur liest. Ich habe viele Fehler gesehen,
die letztlich auf eine versehentliche Änderung eines Attributwerts zurückzuführen waren (oft nach einer nicht
ordnungsgemäß ausgeführten Refactoring-Operation), und auch diese Fehler sind wirklich schwer zu finden, da sie in der
Regel erst viel später in der Anwendung auftreten, und selbst dann nur unter bestimmten Umständen.
Nun zum Datentypbezeichner – was bringt es, zwischen elementaren Feldern, Strukturen, Tabellen und Referenzen zu
unterscheiden? Hauptsächlich Bequemlichkeit, aber es gibt einige Fälle, in denen diese Präfixe wirklich helfen können,
Probleme zu erkennen. ABAP hat eine Geschichte von besonderen Konvertierungsregeln, und die, die ich hier meine, ist die
Konvertierung zwischen flachen Strukturen und
Einzelfeldern. Nehmen wir an, Sie codieren ein
Datenobjekt für eine übersetzbare Entität (etwas mit einer separaten sprachabhängigen Tabelle, die die übersetzbaren
Teile des Objekts enthält), und Sie speichern den Text in einem Attribut namens description
. Eines Tages stellen Sie
fest, dass Sie vergessen haben, eine Accessor-Methode dafür hinzuzufügen, und schreiben einen Getter, der einen STRING
zurückgibt, etwas so Einfaches wie r_result = description.
Sauber und offensichtlich – bis Sie feststellen, dass Ihre
Anwendung 100DEFoobar
anstelle von Foobar
anzeigt. Hoppla. Anscheinend war description
nicht nur der Text, sondern
Sie haben stattdessen den gesamten Texttabelleneintrag gespeichert (was durchaus sinnvoll ist, insbesondere wenn Sie
mehrere übersetzbare Felder haben). Wenn Sie dieses Attribut gs_description
genannt hätten, hätten Sie dies sofort
bemerkt und diesen Fehler vermieden. Das ist zwar ein einfaches Beispiel, aber ich habe diesen Fehler schon bei
erfahrenen Entwicklern in großen Anwendungen gesehen, und es braucht Zeit und Geduld, um die unerwünschte Kundennummer
im ausgehenden Nachrichtenstrom aufzuspüren.
Bei internen Tabellen tritt dieser Konvertierungsfehler nicht auf, aber es gibt noch eine weitere Eigenart in der ABAP-Syntax, die praktisch jeder Neuling erlernen muss. Betrachten Sie die folgenden zwei Beispiele:
DATA: sflight TYPE TABLE OF sflight.
" irgendwie, auf magische Weise, diese Tabelle mit einigen Werten füllen
LOOP AT sflight ASSIGNING FIELD-SYMBOL(<sflight>).
" irgendeine unwichtige Bedingung...
DELETE TABLE sflight FROM <sflight>.
ENDLOOP.
im Gegensatz zu
DATA: sflight TYPE TABLE OF sflight.
" irgendwie, auf magische Weise, diese Tabelle mit einigen Werten füllen
LOOP AT sflight ASSIGNING FIELD-SYMBOL(<sflight>).
" irgendeine unwichtige Bedingung...
DELETE sflight FROM <sflight>.
ENDLOOP.
Zugegeben, das ist schon grenzwertig, aber die Erfahrenen unter Ihnen werden wissen, wie erfinderisch die Unerfahrenen unter Ihnen werden können, wenn sie mit ABAP in Berührung kommen. Ich hätte daraus einen weiteren Fallstrick-Artikel machen können, aber um es noch einmal zu wiederholen: Das erste Beispiel wird die interne Tabelle ändern, während das zweite Beispiel tatsächlich ausgewählte Inhalte der Datenbanktabelle löschen wird. Das Problem hierbei ist nicht nur, dass dies für die Anwendungsdaten gefährlich ist – Fehler wie diese sind in der Regel so katastrophal in ihren Folgen, dass sie in der Regel frühzeitig erkannt werden –, sondern dass es viel schwieriger ist, die Absicht des Entwicklers zu bestimmen und den Fehler zu lokalisieren. Vergleichen wir dies nun mit einer Version mit Präfix:
DATA: lt_sflight TYPE TABLE OF sflight.
" irgendwie, auf magische Weise, diese Tabelle mit einigen Werten füllen
LOOP AT lt_sflight ASSIGNING FIELD-SYMBOL(<ls_sflight>).
DELETE TABLE lt_sflight FROM <ls_sflight>. " OK - interne Tabelle wird geändert
DELETE TABLE sflight FROM <ls_sflight>. " Syntaxfehler: The field "SFLIGHT" is
" unknown, but there is a field with
" the similar name "LT\_SFLIGHT".
DELETE lt_sflight FROM <ls_sflight>. " ATC meckert über "Non-numeric index
" access on internal table"
" und schlägt korrekte Variante vor.
DELETE sflight FROM <ls_sflight>. " OK - ändert Datenbankinhalte
ENDLOOP.
Außerdem wird es nach einer Weile zur Gewohnheit, LOOP AT lt_
gefolgt von Strg+Leertaste einzugeben, und die
Code-Vervollständigung zeigt nur die lokalen Tabellen an – ohne Ihnen die Flexibilität zu nehmen, einfach LOOP AT <Strg+Leertaste>
einzugeben und trotzdem Zugriff auf die vollständige Liste der Optionen zu haben.
Alles in allem sind Präfixe für Variablennamen nicht so schlimm, wie einige Artikel sie darstellen. Präfixe sind eine Behelfslösung, ein Kompromiss zwischen dem, was wir haben, was wir bekommen können, was wir brauchen und vor allem, was wir nicht wollen. Wenn Sie jemals auf einen der oben erwähnten Fehler gestoßen sind, entweder in Ihrem eigenen Code oder in dem eines anderen, wissen Sie, wovon ich spreche. Wenn nicht (noch nicht?), denken Sie bitte trotzdem an die jüngeren Mitglieder Ihres Teams und an die Personen, die Ihre Nachfolge antreten und Ihren Code pflegen müssen. Jemand könnte sehr dankbar für diese paar zusätzlichen Zeichen sein, die weder beim Tippen noch beim Lesen wirklich Zeit kosten.