Ungarische Notation
Bei der ungarischen Notation handelt es sich um eine von Programmierern verwendete Konvention zur Wahl von Bezeichnern für Variablen, Funktionen usw.
Der Name rührt zum einen daher, dass der Erfinder der Konvention, Charles Simonyi, ein Ungar ist, zum andern daher, dass die "ungarisch" notierten Bezeichner tatsächlich eine gewisse Ähnlichkeit zum Ungarischen aufweisen und für Neulinge oft "fremdländisch" klingen.
Die von Simonyi entwickelte Konvention wurde bei Microsoft in der Application Group (Excel, Word etc.) mit großem Erfolg angewendet und in der Folge von der Systems Group (Windows) übernommen, wobei es zu einem grundlegenden Missverständnis kam. Simonyi spricht in seinem Paper vom "type" einer Variablen, was vielfach als "Datentyp" interpretiert wurde. Gemeint ist vielmehr die _Art_ der Variablen im spezifischen Kontext einer Applikation. Es geht also nicht so sehr darum , ob eine Variable Ganzzahl oder Kommazahl ist, sondern ob es sich um einen Zähler handelt, eine Koordinate auf dem Bildschirm, einen Index in einem Array o. ä.
Durch diese Zweideutigkeit existieren zwei Strömungen der Ungarischen Notation, das "Apps Hungarian", welches die echte Notation im Sinne Simonyis ist, und das "Systems Hungarian", welches die durch Fehlinterpretation von Microsoft entstandene ist. Letzteres ist für den schlechten Ruf der Konvention verantwortlich, weil die Benennung einer Variablen nach dem Datentyp wenig zum Verständnis des Inhalts beiträgt und trotzdem viel Aufwand verursacht.
Kern der ungarischen Notation ist es, die Aufgabe und den Typ ("Apps Hungarian") bzw. nur Typ ("Systems Hungarian") einer Variable (oder Methode) im Variablennamen (Methodennamen) zu verdeutlichen.
Apps Hungarian
Zusammensetzung eines Variablennamens
Charles Simonyis Ungarische Notation beschreibt den kompletten Namen einer Variablen. Er will vor allem so aussagekräftige Variablennamen wie var hilf: Integer; ausschließen.
Zu diesem Zweck ist klar definiert, welche Attribute ein Variablennamen enthalten darf;
{Präfix} {Datentyp} {Bezeichner}
Dabei werden Präfix und Datentyp konsequent klein geschrieben, und der erste Buchstabe des Bezeichners groß. Der Unterstrich (_) sollte grundsätzlich vermieden werden. Beispiel:
var idFirst: Integer;
In diesem Beispiel ist i der Präfix (für den Index in einem Array), d der Datentyp (für Double, also eine Gleitkommazahl) und First der Bezeichner (für das erste (engl. first = dt. das Erste) Element eines Arrays). Wichtig ist, dass die Variable idFirst ein Integer ist, obwohl im Datentyp ja ein d für Double notiert ist. Dies liegt daran, dass es sich um eine Laufvariable zu einem Array von Double-Werten handelt. Der physikalische Datentyp der Variable selbst, wird in dem Namen der Variablen gar nicht aufgeführt, weil er für Ihre Aufgabe irrelevant ist.
In den meisten Fällen reicht sogar nur ein Präfix und ein Datentyp, da alle Attribute optional sind. So kann man eine Laufvariable in einer for-Schleife auch einfach id nennen und erreicht damit eine eindeutigere Kennzeichnung als mit einem Variablennamen wie lauf.
Präfixe
Das am strengsten Sinn-bezogene Attribut des ungarischen Variablennamens ist der Präfix. Er nimmt nur Bezug auf Funktion der Variablen im Programm, in dem sie verwendet wird.
Die nun aufgeführten Präfixe, sind die bereits vereinbarten, man kann allerdings jederzeit neue (eigene) verwenden, um neune Aufgaben zu spezifizieren (Dokumentation nicht vergessen!). In der Regel erweisen sich diese Präfixe allerdings als absolut ausreichend.
Präfix | Bedeutung |
---|---|
p | Ein Zeiger zu einer Adresse. Abgeleitet vom engl. pointer. |
h | Ein Zeiger auf einen Zeiger, also äquivalient zu pp. Fast immer wird h im Zusammenhang mit der Kommunikation mit dem Betriebssystem benutzt. Abgeleitet vom engl. handle. |
rg | Ein Array, welches durch "normale" Integer indiziert wird. Das Array rg kann als Intervall einer mathematischen Funktion verstanden werden, bei der jeder ganzen Zahl ein Element zugeordnet wird. Ein rgd etwa ist ein Array das Gleitkommazahlen, doppelter Genauigkeit, enthält. rg ist abgeleitet vom engl. range. |
mp | Ebenfalls ein Array, mit dem Unterschied zu rg, dass hier zum indizieren beliebige Datentypen verwendet werden, zu dem Präfix mp werden also zwei Datentypen notiert, nämlich zunächst der Datentyp des Indizes, und dann der Datentyp des Inhalts. Ist x ein beliebiger Datentyp, so ist mpix äquivalent zu rgx. mp ist abgeleitet vom engl. map. |
dn | Noch ein Präfix für ein Array, die besonderheit von dn ist, dass dieser Präfix betont, dass das wichtige nicht die Elemente des Arrays, sondern die Indizes selbst sind, was dieses Präfix sehr selten macht. Abgeleitet vom engl. domain. |
i | Einer der wichtigsten Präfixe im Zusammenhang mit Arrays ist i für den Index des Arrays. So indiziert id ein rgd. Bei einem mpfr, also einem Array von Gleitkommawerten, indiziert von einem boole'schen Datentyp, kann man den Index als ifr oder schlicht ir deklarieren (obwohl er einen boole'schen Datentyp haben muss). |
b | Ein sehr seltener Präfix, der jedoch ähnlich wie i ist, nur, dass b den direkten Offset eines Elementes in einem Array beschreibt. Ist das Array vom physikalischen Datentyp Byte, so sind b und i sogar gleich. Ist dch die physikalische Länge der Elemente eines Arrays rgx, dann gilt für den Index ix (beginnend bei 0): bx = dch * ix. b ist abgeleitet vom engl. base. |
e | Das Pendant zu i. e kennzeichnet ein Element eines Arrays und wird meistens in Verbindung mit dn genutzt und ist dementsprechend selten. Dennoch kann auch ein Element des Arrays rgd mit ed bezeichnet werden, auch wenn dies in den meisten Fällen nicht Zweckdienlich ist. |
c | Eine Anzahl von Elementen, etwa in einem Array. Die Größe eines grul kann als cul angegeben werden. Der Name ist abgeleitet vom engl. count. |
d | Ein Unterschied zwischen zwei Variablen, meistens in einem Array. Dabei sollte man nicht den Fehler machen, d mit c zu verwechseln. d bezieht sich immer auf eine Differenz zwischen Indizes. |
f | Ein Bit in einer Veriable. Nicht zu verwechseln, mit dem Datentyp f oder bit, der die ganze (physikalische) Variable meint. Der Präfix f meint ein Bit in einer Variable des physikalischen Datentyps byte, word, ect., das einen Flaggencharakter hat. Abgeleitet vom engl. flag. |
sh | Der Index zu einem Bit (f) in einer Variablen (keinem Array). Ist nur f gesetzt hat die Variable den Wert 2^sh. sh ist abgeleitet vom engl. shift amount. |
u | Eine unspezifische Variable, die verschiedene (ungarische) Datentypen beinhalten kann (und dies auch sinnvoll ist). Daher ist dieses Präfix äußerst selten, denn zwei sinnverschiedene Variablen, sind selten kompatibel. Abgeleitet vom engl. union. |
a | Eine Zuordnung, kein Array. a wird als Komplement zu p bzw. h verwendet, da in a die Dereferenzierung gespeichert wird. Damit ist apl äquivalent zu l, da es die Variable, an der Adresse von l ist, also l selbst. a ist abgeleitet vom engl. allocation. |
v | Eine globale Variable. Etwa zum Austausch von Daten. Sollte in der Praxis sparsam bis gar nicht eingesetzt werden, da es dazu verleitet, den Sinn der Variablen auszulassen. Wenn die Variable keinen strengen Zweck hat, ist es meist besser den Präfix einfach wegzulassen, als ein konstruiertes v zu notieren. |
Datentypen
Um nun eine bessere Austauschbarkeit von Quellcode zu erreichen hat man (bzw. Simonyi) sich auf einige Datentypen, oder Basetypes, geeinigt. Dabei "schmeckt" man einen leichten C-Geschmack heraus, was die Benennung betrifft (zum Beispiel l wie long für einen 32-bit Integer).
Datentyp | Bedeutung |
---|---|
f | Boole'sche Datentypen (gemeint ist wieder die Bedeutung, nicht der physikalische Datentyp), bzw. Variablen, mit Wahrheitswert. Der Bezeichner sollte denjenigen Zustand beschreiben, wenn die Variable wahr, also true ist. Abgeleitet vom engl. flag. |
ch | Ein-Byte Zeichen. Meistens in einem vorzeichenunbehafteten Byte oder einem Char gespeichert. Abgeleitet vom engl. character. |
st | Eine "Pascal"-Zeichenkette, also eine Zeichenkette, dessen erstes Zeichen die Länge des Strings enthält. Abgeleitet vom engl. string. |
sz | Ein Nullterminierter String, wie er in C implementiert ist; (char *) |
fn | Meistens ein Zeiger auf eine Methode. Abgeleitet vom engl. function |
fl | Eine Datei- bzw. Datenstruktur, meistens von dem Betriebsystem übergeben. Abgeleitet vom engl. file |
w | Ein Maschinenwort, meistens zwei Byte groß und vorzeichenbehaftet. Gemeint ist allerdings nicht die zwingende Implementation in dem physikalischen Datentyp word. Wie bei der AppsUN üblich, ist der Zweck gemeint. Etwa eine generische Benutzung der Variablen mit entsprechenden Methoden kann ein w rechtfertigen. |
b | Ein Byte, welches ebenfalls nicht an den gleichnamigen physikalischen Datentyp gebunden ist (siehe w). |
l | Ein Doppelwort, also vier Byte, ebenfalls nicht an long(C) oder Integer(Pascal) gebunden (siehe w). Abgeleitet aus dem engl. long. |
uw | Vorzeichen unbehaftetes Maschinenwort. Abgeleitet vom engl. unsigned word. |
ul | Vorzeichen unbehaftetes Doppelwort. Abgeleitet vom engl. unsigned long. |
r | Gleitkommawert mit einfacher Genauigkeit. In C etwa float. Abgeleitet vom engl. real. |
d | Gleitkommawert mit doppelter Genauigkeit (double). Abgleitet vom engl. double. |
bit | Ein einzelnes Bit. Kann meist besser mit einem 'f' (flag) bezeichnet werden. |
v | Eine leere Variable, ohne Datentyp. Wird nur in Verbingung eines Zeigers verwendet (pv). Abgeleitet vom engl. void. |
env | Wird für Labels, also Sprungziele verwendet (Pascal: goto envLoop;). Abgeleitet vom engl. environment. |
sb | Ein Segmentzeiger auf dem Speicher (siehe Assembler). Abgeleitet vom engl. segment base. |
ib | Eine Offsetadresse. Tatsächlich hat dieser Name zwei Gründe. Zunächst kann es als Index (i) zu einem Array von Bytes (b) angesehen werden, wenn man sich den RAM als Array vorstellt. Zum zweiten kann man ib auch von indivisible base ableiten. |
Bezeichner
Oft reichen Präfix und Datentyp völlig aus um eine Variable zu benennen und zu erklären. Die Variable, zum Durchlaufen eines Arrays rgch, ist duch
var ich: Integer; // Pascal int ich; // C
ausreichend beschrieben. Jedes Beiwort erscheint überflüssig, "nich-ungarisch" oder schlicht falsch. Zum Beispiel: ichLauf, ichIndex, ichArray, ect.
Trotzdem benötigt man gelegentlich einen Bezeichner, der die Variable konkret an eine Aufgabe bindet. Dazu kann man ein beliebiges (selbstverständlich Sinnvolles) Wort anhängen. Man muss nur beachten, keine Unterstriche (_) zu benutzen, und das Wort nach der Form: "Xxxxx" zu notieren (also nur den ersten Buchstaben groß zu schreiben). Zu diesem Zweck gibt es bereits einige vereinbarte Wörter, die man aufgrund ihrer häufigen Benutzung eingeführt hat. Davon beziehen die meisten sich auf ein Array oder eine ähnliche Struktur.
Bezeichner | Bedeutung |
---|---|
Min | Beschreibt das allererste Element eines Arrays und wird oft im Zusammenhang mit einem Zeiger oder einem Index gebracht; pchMin, ichMin. |
Mic (>=Min) | Ähnelt sehr Min, beschreibt jedoch das physikalisch kleinste Element, welches in der Praxis fast immer auch Min ist. |
First (>=Mic) | Beschreibt das erste Element eines Arrays, welches benutzt werden soll. Ist oft an den Präfix i gebunden; ichFirst. |
Last (>=First) | Eine Variable xxLast ist das Pendant zu xxFirst. Sie wird benutzt um das letzte Element eines Array zu indizieren. |
Most (>=Last) | In gewisser Hinsicht das Gegenstück zu Min, da es den höchsten Index eines Arrays angibt. |
Lim (>Most) | Mit Lim wird die Anzahl der Elemente in einem Array angegeben. Damit ist der Index, mit dem Namen xxLim größer als das letze Element und damit ungültig. |
Mac (>=Lim) | Das Gegenstück zu Mic und damit Max sehr ähnlich. Wie Lim, ein ungültiger Index. |
Max (>=Mac) | Pendant zu Min, wird genutzt um die tatsächliche Anzahl an Elementen eines Arrays anzugeben. Dieser Index ist ebenfalls ungültig. |
Keinen Bezug mehr zu einem Array | |
Sav | Wird als temporärer Speicherplatz für den Wert einer Variablen benutzt. Zu häufiges Benutzen eines Sav Bezeichners ist jedoch stilistisch ebenso fragwürdig wie der Präfix v, da die Variable, keine strenge Namensbindung mehr besitzt. Abgeleitet vom engl. Save. |
T | Ähnlich wie Sav, nur dass dieser Bezeichner, die Betonung auf noch kürzere Auslagerungen von Daten legt und somit noch weniger Namensgebunden ist. T sollte man erst recht vermeiden, vor allem jedoch die Produktion vieler "temporärer" Variablen duch wiederholtes Anhängen von T. Namen, wie xxTTT, xxTTTT, oder gar xxT5 sind Anzeichen für falsches Ungarisch und sollten grundsätzlich nicht benutzt werden. |
Nil | Kennzeichnet einen ungültigen Wert, und wird demzufolge, sinnvollerweise meist als Konstante verwendet (siehe Implementation von Pascal: nil). Meistens sind dort Werte enthalten, wie "0", oder "-1". |
Null | Ähnlich wie Nil. Kennzeichnet jedoch meist die Zahl "0", ebenfalls oft als ungültiges Element. Um Missverständnissen vorzubeugen, sollte eine gleichzeitige Benutzung von Nil und Null vermieden werden, oder ggf. konkret Kommentiert werden. |
Src | Dieser Bezeichner wird benutzt um zu spezifiziern, dass es sich bei der Variablen um eine Quelle (engl. source) handelt. Etwa bei einem Transportalgorithmus. |
Dest | Dest wird oft in Verbindung mit Src benutzt und verweist auf das Ziel (engl. destination) einer Operation (deren Quelle Src ist). |
Darüber hinaus lassen sich natürlich beliebig andere Bezeichner wählen. Jedoch sollte man sich bemühen zunächst die Bezeichner der Tabelle zu verwenden. Dies gilt vor allem in Bezug auf Arrays. Zum Beispiel; In Pascal, gibt die Funktion
Length(grx);
die Länge des Arrays grx zurück. So ist es verlockend, das Ergebniss in eine Variable culLength zu speichern. Falsch ist diese Lösung nicht, da die Benutzung der Bezeichner nicht streng ist. Jedoch ist es wünschenswert culMax zu verwenden, um Standardkonform zu programmieren.
Beispiele
Beispiel | Bedeutung |
---|---|
rgch | Ein Array von Zeichen, anders ausgedrückt eine Zeichenkette. Diese Notation ist äquivalent zu sz (oder je nach Implementation zu st). |
ast | Der Wert an der Stelle auf die st zeigt, also das erste Element einer pascal'schen Zeichenkette und damit die Anzahl der Elemente. Gleichbedeutend zu cst. |
uuluwch | Eine Variable, die sowohl 32-bit, 16-bit wie auch Acht-bit große Zahlen speichert. In der Praxis würde man sich vermutlich mit einem ul begnügen. Es sei denn es soll explizit darauf aufmerksam gemacht werden, dass die Zahlen, einer bestimmten Menge von Datentypen angehören. |
rgbit | Eine Ansammlung von Marken. Man könnte diese Variable als einen long implementiern. Hier ist der große Vorzug der Ungarischen Notation. An einem long flags; könnte man schwer eine Bedeutung der Variable flags herauslesen. Aber ein long rgbit; verweist deutlich auf den Charakter einer Sammlung von Marken hin. |
Systems Hungarian
Achtung! Diese Notation ist die Abwandlung der microsoft'schen Windowsprogrammierer und entspricht nicht mehr dem Sinn, den Simonyi bei der Entwicklung der ungarischen Notation verfolgt hat.
Beispiele
Präfix | Datentyp | Beispiel |
---|---|---|
n | Integer | nSize |
b | Boolean | bBusy |
sz | null-terminierter String | szLastName |
p | Zeiger | pMemory |
a | Array | aCounter |
c | char | cName |
dw | Double Word, 32 Bit | dwNumber |
w | Word, 16 Bit | wNumber |
Die einzelnen Präfixe lassen sich auch kombinieren. So definiert paszTabelle einen Zeiger auf ein Array null-terminierter Strings.
Zusätzlich lassen sich Präfixe für Variablensichtbarkeit definieren:
Präfix | Sichtbarkeit | Beispiel |
---|---|---|
m_ | Member-Variable | m_szLastName |
p_ | Methodenparameter | p_iNewValue |
s_ | statische Variable | s_iInstanceCount |
Betrachtung
Vorteile
- Der Datentyp ist aus dem Variablennamen ersichtlich.
Nachteile
- Es werden verschiedene, geringfügig unterschiedliche Versionen der ungarischen Notation benutzt, was zu Verwirrungen führen kann.
- Verleitet zu uninformativen Variablennamen, der Quelltext ist schwerer lesbar.
- Wenn sich der Typ einer Variablen ändert, muss der Name im gesamten Programm ersetzt werden. Im besten Falle macht das das Refactoring aufwendig, im schlechtesten widersprechen sich Typ und Präfix, was zu Verwirrung führt.
- Moderne Programmierumgebungen wie Eclipse erlauben dem Programmierer jederzeit den Typ einer Variablen abzufragen. Bei Inkonsistenzen warnen sie automatisch und weisen auf mögliche Fehler hin. Die Ungarische Notation wird damit praktisch überflüssig.
Fazit
Die Ungarische Notation ist besonders vorteilhaft für nicht streng-typisierte Sprachen wie C, für die sie ursprünglich entwickelt wurde. In modernen, objektorientieren Hochsprachen wie Java oder C# wird sie aber heute kaum noch verwendet. Ihre Hauptintention, nämlich die Zuordenbarkeit von Typ zu Variable, wird von aktuellen Entwicklungsumgebungen besser und zuverlässiger geboten. Hier überwiegen die Nachteile der ungarischen Notation. In Microsofts neuem .NET-Framework findet sie etwa keine Anwendung mehr.
Weblinks
- Joel on Software - Making Wrong Code Look Wrong - Einige Anmerkungen über Simonyis wahre Intention hinter der Ungarischen Notation
- Meta-Programming: A Software Production Method. Charles Simonyi, Dezember 1976 (PhD Thesis)
- Charles Simonyis eigene Erklärung der Ungarischen Notation
- HTML-Version von Doug Klunders Memo