Ungarische Notation
Bei der ungarischen Notation handelt es sich um eine von Programmierern verwendete Namenskonvention zur Wahl von Bezeichnern für Variablen und Konstanten, Funktionen und Methoden sowie anderen Objekten.
Ihren Namen verdankt die ungarische Notation dem in einem (englischen) Programmtext exotisch anmutenden Aussehen der durch bestimmte Regeln zustande gekommenen Bezeichner und der ungarischen Herkunft ihres Erfinders Charles Simonyi.[1]
Die von Simonyi entwickelte Konvention wurde bei Microsoft in der Application Group (Microsoft Office) mit großem Erfolg angewandt 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 Aufgabe einer Variablen im spezifischen Kontext einer Applikation. Es geht also nicht darum, ob eine Variable eine Ganz- oder Bruchzahl speichert, sondern, ob sie einen Zähler, eine Koordinate auf dem Bildschirm, oder einen Index in einem Array repräsentiert. Aus dem Variablennamen sollte man also die Bedeutung und nicht ihren Speichertyp erschließen können.
Durch diese Doppeldeutigkeit existieren zwei Strömungen der Ungarischen Notation, das Apps Hungarian, welches die echte Notation im Sinne Simonyis ist, und das Systems Hungarian, welches durch die Fehlinterpretation der Microsoftschen Betriebssystemabteilung entstanden 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) in deren Namen zu verdeutlichen.
Apps Hungarian
Zusammensetzung eines Variablennamens
Charles Simonyis Ungarische Notation beschreibt den kompletten Namen einer Variablen. So will er vor allem wenig aussagekräftige Variablennamen wie var hilf: Integer;
ausschließen.
Zu diesem Zweck ist klar definiert, welche Attribute ein Variablenname 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: Byte; // Pascal byte idFirst; // C
In diesem Beispiel ist i das Präfix (für den Index in einem Array), d der Datentyp (für Double, s. u.) und First der Bezeichner (für das erste [englisch first = deutsch 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 physische 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 reichen 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 aussagekräftigere Kennzeichnung als mit einem Variablennamen wie lauf.
Präfixe
Das am strengsten sinn-bezogene Attribut des ungarischen Variablennamens ist das Präfix. Es nimmt nur Bezug auf die Funktion der Variablen im Programm, in dem sie verwendet wird.
Die nachstehend aufgeführten Präfixe sind die bereits vereinbarten. Man kann allerdings jederzeit neue (eigene) verwenden, um neue Aufgaben zu spezifizieren. In der Regel erweisen sich die folgenden Präfixe allerdings als absolut ausreichend.
Präfix | abgeleitet von | Bedeutung |
---|---|---|
p | pointer | Ein Zeiger zu einer Adresse. |
k | Eine Konstante. | |
h | handle | Ein Zeiger auf einen Zeiger, also äquivalent zu pp. Fast immer wird h im Zusammenhang mit der Kommunikation mit dem Betriebssystem benutzt. |
rg | range | 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. |
mp | map | Ebenfalls ein Array, mit dem Unterschied zu rg, dass hier zum Indizieren beliebige Datentypen verwendet werden, zum Präfix mp werden also zwei Datentypen notiert, nämlich zunächst der Datentyp des Index, und dann der Datentyp des Inhalts. Ist x ein beliebiger Datentyp, so ist mpix äquivalent zu rgx. |
dn | domain | Noch ein Präfix für ein Array: Die Besonderheit von dn ist, dass dieses Präfix betont, dass das Wichtige nicht die Elemente des Arrays, sondern die Indizes selbst sind, was dieses Präfix sehr selten macht. |
i | index | Eines der wichtigsten Präfixe im Zusammenhang mit Arrays. Z. B. indiziert id ein rgd. Bei einem mpfr, also einem Array von Gleitkommawerten, indiziert von einem booleschen Datentyp, kann man den Index als ifr oder schlicht ir deklarieren (obwohl er einen booleschen Datentyp haben muss). |
b | base | Ein sehr seltenes Präfix, das jedoch ähnlich wie i ist, nur, dass b den direkten Offset eines Elementes in einem Array beschreibt. Ist das Array vom physischen Datentyp Byte, so sind b und i sogar gleich. Ist dch die physische Länge der Elemente eines Arrays rgx, dann gilt für den Index ix (beginnend bei 0): bx = dch * ix. |
e | element | 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 | count | Eine Anzahl von Elementen, etwa in einem Array. Die Größe eines rgul kann als cul angegeben werden. |
d | difference | 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. |
gr | group | Nicht mit rg zu verwechseln: gr bezeichnet einen Verbund von mehreren Variablen. Dabei handelt es sich jedoch nicht um ein Array, sondern eine Anordnung von unterschiedlichen Variablen. gr kann bei einem struct, record oder einer class benutzt werden. |
f | flag | Ein Bit in einer Variablen. Nicht zu verwechseln mit dem Datentyp f oder bit, der sich auf die ganze (physische) Variable bezieht. Das Präfix f bezeichnet ein Bit in einer Variablen des physischen Datentyps byte, word, etc., das Flaggencharakter hat. |
sh | shift amount | Der Index zu einem Bit (f) in einer Variablen (keinem Array). Ist nur f gesetzt, hat die Variable den Wert . |
u | union | Eine unspezifische Variable, die unterschiedliche (ungarische) Datentypen beinhalten kann (wenn dies auch sinnvoll ist). Daher ist dieses Präfix äußerst selten, denn zwei sinnverschiedene Variablen sind selten kompatibel. |
a | allocation | Eine Zuordnung, kein Array. a wird als Komplement zu p oder auch 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. |
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, das Präfix einfach wegzulassen, als ein konstruiertes v zu notieren. |
Datentypen
Um eine bessere Austauschbarkeit von Quellcode zu erreichen, hat man (bzw. Simonyi) sich auf einige Datentypen oder Basetypes, geeinigt. Dabei stellt man einen leichten „C-Geschmack“ fest, was die Benennung betrifft (zum Beispiel l wie long für einen 32-Bit-Integerwert).
Datentyp | abgeleitet von | Bedeutung |
---|---|---|
f | flag | Boolesche Datentypen (gemeint ist wieder die Bedeutung, nicht der physische Datentyp) bzw. Variablen mit Wahrheitswert. Der Bezeichner sollte den wahr-Zustand der Variablen beschreiben, wenn sie also true ist. |
ch | char(acter) | Ein Ein-Byte-Zeichen. Meistens in einem nicht vorzeichenbehafteten (unsigned) Byte oder einem Char gespeichert. |
st | string | Eine Zeichenkette, die ähnlich derer in der Programmiersprache Pascal ist, also eine Zeichenkette, deren erstes Zeichen die Länge des Strings enthält. |
sz | string zero terminated | Ein nullterminierter String, wie er in C implementiert ist (Zeigerbasiertes char-Array) |
fn | function | Meistens ein Zeiger auf eine Methode. |
fl | file | Eine Datei bzw. eine Datenstruktur, meistens vom Betriebssystem übergeben. |
w | word | Ein Maschinenwort, meistens zwei Byte groß und vorzeichenbehaftet. Gemeint ist allerdings nicht zwingend die Implementation im physischen 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 | byte | Ein Byte, welches ebenfalls nicht an den gleichnamigen physischen Datentyp gebunden ist, diesem allerdings aufgrund der vorzeichenlosen 8 Bit meistens entspricht (siehe w). |
l | long | Ein Doppelwort, also vier Byte, ebenfalls nicht an long (C) oder Integer (Pascal) gebunden (siehe w). |
uw | unsigned word | Nicht vorzeichenbehaftetes Maschinenwort. |
ul | unsigned long | Nicht vorzeichenbehaftetes Doppelwort. |
r | real | Gleitkommawert mit einfacher Genauigkeit. In C meist float. |
d | double | Gleitkommawert mit doppelter Genauigkeit. In C meist double. |
bit | Ein einzelnes Bit. Kann meist besser mit einem ‚f‘ (flag) bezeichnet werden. | |
v | void | Theoretisch eine leere Variable ohne Datentyp. Wird nur in Verbindung mit einem Zeiger verwendet, um typenunabhängig auf Werte zu zeigen. |
env | environment | Wird für Labels, also Sprungziele verwendet (Pascal: goto envLoop;). |
sb | segment base | Ein Segmentzeiger auf den Speicher (siehe Assemblersprache). |
ib | indivisible base oder index byte | Zunächst kann die Variable als Index (i) zu einem Array von Bytes (b) angesehen werden. Allerdings 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 durch
var ich: Integer; // Pascal int ich; // C
ausreichend beschrieben. Jedes Beiwort erscheint überflüssig, "nicht-ungarisch" oder schlicht falsch. Zum Beispiel: ichLauf, ichIndex, ichArray etc.
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 Verwendung eingeführt hat. Davon beziehen sich die meisten auf ein Array oder eine ähnliche Struktur.
Bezeichner | Bedeutung |
---|---|
Bezogen auf Arrays | |
Min | Beschreibt das allererste Element eines Arrays und wird oft im Zusammenhang mit einem Zeiger oder einem Index gebraucht; pchMin, ichMin. |
Mic (>=Min) | Ähnelt sehr Min, beschreibt jedoch das physisch kleinste Element, welches in der Praxis fast immer auch Min ist. |
First (>=Mic) | Beschreibt das erste zu benutzende Element eines Arrays. Ist oft an das Präfix i gebunden; ichFirst. |
Last (>=First) | Eine Variable xxLast ist das Pendant zu xxFirst. Sie wird benutzt, um das letzte Element eines Arrays 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 letzte 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 Wert ist als Index auf einen Array ebenfalls ungültig. |
Ohne Bezug zu einem Array | |
Nil | Kennzeichnet einen ungültigen Wert, und wird demzufolge sinnvollerweise meist als Konstante verwendet (siehe Pascal: nil[2]). Meistens sind dort Werte wie „0 “ oder „-1 “ enthalten.
|
Null | Ähnlich wie Nil. Kennzeichnet jedoch meist die Zahl „0 “, ebenfalls oft als ungültiges Element, entspricht relativ genau der C- und C++-Compilerkonstante NULL[3]. 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 spezifizieren, 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). |
Sav | Wird als temporärer Speicherplatz(engl. Save) für den Wert einer Variablen benutzt. Zu häufiges Benutzen dieses Bezeichners ist jedoch stilistisch ebenso fragwürdig wie das Präfix v, da die Variable keine strenge Namensbindung mehr besitzt. |
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 durch wiederholtes Anhängen von T. Namen wie xxTTT, xxTTTT oder xxT5 sind Anzeichen für falsch umgesetzte Ungarische Notation und sollten grundsätzlich gemieden werden. |
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 gibt die Funktion
Length(rgx);
in Pascal die Länge des Arrays rgx zurück. So ist es verlockend, das Ergebnis in einer 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 mit cst. |
uuluwch | Eine Variable, die sowohl 32-bit, 16-bit wie auch 8-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 implementieren. Hier ist der große Vorzug der Ungarischen Notation. Aus 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. |
rggr | Ein Array, dessen Elemente Verbunde oder Klassen sind. |
mpchgr | Ebenfalls ein Array, dessen Elemente Verbunde oder Klassen sind. Allerdings mit dem Unterschied zu rggr, dass das Array von ich indiziert wird, also von positiven Bytes. |
Systems Hungarian
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.
Zusammensetzung des Variablennamens
Präfix und Bezeichner
Anders als beim Apps Hungarian wird der Bezeichner nur aus dem Präfix, welcher dem Datentyp entspricht, und dem frei gewählten Namen zusammengesetzt.
Präfix | Datentyp | Beispiel |
---|---|---|
n | Integer | nSize |
b | Boolean | bBusy |
sz | null-terminierter String | szLastName |
ch | char | chName |
dw | Double Word, 32 Bit, unsigned | dwNumber |
w | Word, 16 Bit, unsigned | wNumber |
p | Zeiger | pMemory |
a | Array | aCounter |
Die einzelnen Präfixe lassen sich auch kombinieren. So definiert paszTabelle einen Zeiger auf ein Array null-terminierter Strings.
Präfixe der Sichtbarkeit
Zusätzlich lassen sich Präfixe für Variablensichtbarkeit definieren:
Präfix | Sichtbarkeit | Beispiel |
---|---|---|
m_ | Member-Variable | m_szLastName |
p_ | Methodenparameter | p_nNewValue |
i_ | Interfaceparameter (Argument von Funktionen) | i_nNewValue |
s_ | statische Variable | s_nInstanceCount |
g_ | globale Variable | g_nTimestamp |
Kritik
Einwände gegen die ungarische Notation
“… nowadays HN and other forms of type encoding are simply impediments. They make it harder to change the name or type of a variable, function, member or class. They make it harder to read the code. And they create the possibility that the encoding system will mislead the reader.”
„Heutzutage stellen die ungarische Notation und andere Formen der Typcodierung einfach nur Hindernisse dar. Sie machen es schwerer den Namen oder Typ einer Variablen, Funktion, Felds oder Klasse zu ändern. Sie erschweren es den Code zu lesen. Und sie erzeugen die Möglichkeit, dass das Codierschema für den Leser irreführend ist.“
“Encoding the type of a function into the name (so-called Hungarian notation) is brain damaged – the compiler knows the types anyway and can check those, and it only confuses the programmer.”
„Die Kodierung des Typen einer Funktion in den Namen (die sogenannte ungarische Notation) ist hirnrissig. Der Compiler kennt die Typen ohnehin und kann diese überprüfen und es verwirrt nur den Programmierer.“
“No I don’t recommend 'Hungarian'. I regard 'Hungarian' (embedding an abbreviated version of a type in a variable name) a technique that can be useful in untyped languages, but is completely unsuitable for a language that supports generic programming and object-oriented programming – both of which emphasize selection of operations based on the type and arguments (known to the language or to the run-time support). In this case, 'building the type of an object into names’ simply complicates and minimizes abstraction.”
„Nein, ich empfehle die ungarische Notation nicht. Ich betrachte die ungarische Notation (Einbettung einer abgekürzten Version eines Typs in einem Variablennamen) als eine Technik, welche in untypisierten Sprachen nützlich sein kann, aber völlig ungeeignet für eine Sprache ist, welche generische und objektorientierte Programmierung – beides Techniken die die Auswahl von Operationen basierend auf dem Typ (welcher aufgrund der Sprache oder der Laufzeitunterstützung bekannt ist) eines Arguments – unterstützt. In diesem Fall wird es nur komplizierter und die Abstraktion verringert.“
- Die Framework Design Guidelines (zu dt. Frameworkentwurfsrichtlinien, Vorgaben seitens Microsoft bzgl. der Benennung und des Modells für Bibliotheken, die das .NET Framework erweitern) verbieten Entwicklern den Einsatz der ungarischen Notation, obwohl es bei veralteten Entwicklungsplattformen wie Visual Basic 6 durchaus üblich war.[7] Die Framework Design Guidelines machen jedoch keine Aussage über die Benennung privater Variablen.
Bindung von Feldname an Felddatentyp
Ein weiterer Nachteil der ungarischen Notation ist die erschwerte Migration von Code. Ändert sich der Datentyp eines Feldes, so muss das Feld zwangsläufig umbenannt werden, wodurch Code, der auf der API basiert, ungültig wird und großflächig (z. B. bei einer Umstellung von 32-Bit auf 64-Bit Werten) geändert werden muss. Aus Gründen der Code-Abwärtskompatibilität wird z. B. im Falle der WinAPI auf eine Änderung des Feldnamens verzichtet, wodurch die ungarische Notation auf einen obsoleten Feldtyp hinweist. Beispiel:[8]
Win16: WndProc(HWND hW, WORD wMsg, WORD wParam, LONG lParam) Win32: WndProc(HWND hW, UINT wMsg, WPARAM wParam, LPARAM lParam)
Weblinks
- Roedy Green – How to Write Unmaintainable Code (Punkt 30.)
- .NET Framework Entwicklerhandbuch Allgemeine Benennungskonventionen. Offizielle Empfehlung von Microsoft für das .NET-Framework
- Dr.Dobbs Journal – Conversations: Hungarian wartHogs
- Joel on Software – Making Wrong Code Look Wrong – Einige Anmerkungen über Simonyis wahre Intention hinter der Ungarischen Notation
- Charles Simonyi: Meta-Programming: A Software Production Method. (gzip Postscript) Dezember 1976 (PhD Thesis)
- Charles Simonyis eigene Erklärung der Ungarischen Notation
- Peter GAAL − Ein Alphabet der Ideen Wieso Ungarisch tatsächlich ähnlich funktioniert, wie die ungarische Notation
- HTML-Version von Doug Klunders Memo
Einzelnachweise
- ↑ Charles Simonyi: Hungarian Notation. In: MSDN. Microsoft, 1999, abgerufen am 30. Juli 2014 (englisch).
- ↑ Nil/de - Free Pascal wiki. Abgerufen am 18. Juli 2018 (englisch).
- ↑ NULL – cppreference.com. Abgerufen am 18. Juli 2018.
- ↑ Robert C. Martin: Clean Code: A Handbook of Agile Software Craftsmanship. Hrsg.: Prentice Hall PTR. 1. Auflage. Redmond WA 2008, ISBN 978-0-13-235088-4.
- ↑ Linux kernel coding style. In: Linux (Kernel) Dokumentation. Abgerufen am 10. August 2019 (englisch).
- ↑ Bjarne Stroustrup: Bjarne Stroustrup’s C++ Style and Technique FAQ. 8. Juni 2014, abgerufen am 30. Juli 2014.
- ↑ General Naming Conventions. In: MSDN. Microsoft, abgerufen am 30. Juli 2014 (englisch).
- ↑ What do the letters W and L stand for in WPARAM and LPARAM? - The Old New Thing - Site Home - MSDN Blogs