Konventionen für die Programmierung
Variablen (Die ungarische Notation)
Beispiele für die ungarische Notation
Der Sinn von Konventionen
Konventionen sind ein lästiges, aber nötiges Übel, wenn man im Team an einem Projekt arbeitet. Standards und Konventionen werden oft als einschränkendes Korsett empfunden, durch das die Kreativität ausgebremst wird. Aber Konventionen haben unbestreitbare Vorteile:
Der Glaubenskrieg
Über Konventionen kann man sich hervorragend streiten. Eigentlich jeder Programmierer entwickelt mit der Zeit seine eigenen Konventionen. Diese dann einmal zu ändern heißt eine Gewohnheit zu ändern - und das ist lästig. Oft wird über Konventionen so gestritten als ginge es um Religion. Dabei wird dann der wesentliche Punkt einer Konvention übersehen, nämlich der, daß es überhaupt eine Konvention gibt. Was ich damit sagen will: Es ist eigentlich egal, was für Konventionen man vereinbart, wichtig ist nur, das man sie vereinbart. Jede Konvention ist besser als keine!
In diesem Sinne möchte ich die hier vorgestellten Konventionen als einen Vorschlag einbringen - und nicht als den Stein der Weisen. Der größte Teil stammt aus der Windows-Welt, ein anderer Teil aus meiner betrieblichen Praxis.
Dieses Dokument beschreibt Namenskonventionen für Typen, Konstanten, Funktionen und Variablen, wie sie unter Microsoft Windows üblich sind.
Konstanten und Aufzählungen werden in Windows immer groß geschrieben, einzelne Wortteile getrennt durch den Unterstrich. Es ist üblich, das in den Namen eine Abkürzung für den Verwendungszweck enthalten ist. So heißen die Konstanten für die Api-Funktion GetWindowLong () z.B. GWL_STYLE, GWL_EXSTYLE, GWL_WNDPROC usw.
Adjektive wie MAX und MIN werden hinten an den Namen angehängt. Dabei bezeichnet MAX oft die Anzahl der Elemente in einem Array, also eins mehr als den größten zulässigen Index:
const MODULES_MAX = 64;
const MODULE_NAME_MAX = 32;
MODULE_DATA g_Modules[MODULES_MAX];
for (int i = 0; i < MODULES_MAX; i++)
...
Benutzerdefinierte Typen (typedef, struct, union, enum) werden, genau wie benannte Konstanten, in Großbuchstaben mit Unterstrich geschrieben. Typen die einen Pointer darstellen, enthalten als ersten Buchstaben ein großes "P". Oft findet man, bei vordefinierten Typen, auch noch das 16-Bit Relikt "LP" für "Long Pointer". Ein Zeiger auf eine Funktion (Callback - Zeiger) beginnt mit "PF":
typedef const char* LPCSTR;
typedef char* LPSTR;
typedef void (WINAPI *PFPRINT) (LPCSTR pszText);
enum MODULE_TYPE
{
MDT_UNKNOWN,
MDT_SIMPLE,
MDT_SMART
};
struct MODULE_DATA
{
char szName [MODULE_NAME_MAX];
MODULE_TYPE Type;
};
Klassen (class) werden in Groß- Kleinschreibung ohne Unterstrich geschrieben. Dem Namen wird außerdem ein großes "C" vorangestellt. OLE-Interfaces wird ein großes "I" vorangestellt.
Die Namen von abstrakten Basisklassen (außerhalb von OLE) enden mit einem großen A. Klassen gelten hier als abstrakt, wenn sie im wesentlichen eine Schnittstelle definieren und es entweder nicht möglich oder nicht sinnvoll ist Instanzen von ihnen zu erzeugen.
class CTrashObj : public CNodeA
{
CTrashObj ();
virtual SetData (LPVOID pData);
...
};
class IClassFactory : public IUnknown
{
HRESULT QueryInterface (REFIID riid, void * * ppvObj) ;
unsigned long AddRef ();
unsigned long Release ();
...
};
Zu Verwirrung führt oft die unterschiedliche Benennung von Klassen und Strukturen. Fängt man nämlich an, einer Struktur Memberfunktionen zu geben (beginnt ganz harmlos: Nur ein Konstruktor für die einfache Initalisierung), so wird diese langsam aber sicher zu einer Klasse. Spätestens wenn die "Struktur" virtuelle Funktionen bekommt, sollte man sie wirklich in eine Klasse überführen.
Funktionsnamen beginnen in Windows mit einem Großbuchstaben, die einzelnen Wortteile werden ebenfalls durch Großbuchstaben getrennt. Ein Funktionsname ist immer nach dem Schema Operation-Objekt[-Attribut] aufgebaut. Beispiele sind: SetThreadPriority (), GetCurrentWindow (), GetFileSecurity (), CreateProcess (), EnterCriticalSection () etc.
Selektoren (Funktionen, die ein Attribut eines Objektes ermitteln) beginnen immer mit "Get", Modifizierer (Funktionen, die ein Attribut eines Objektes verändern) immer mit "Set".
Variablen werden in der Windows-Programmierung durch die ungarische Notation dargestellt.
Die ungarische Notation besteht aus detaillierten Richtlinien zur Benennung von Variablen. Diese Notation hat in der Programmierung in C, besonders in der Windows-Welt, weite Verbreitung gefunden. Sie wird "ungarisch" genannt, weil der Vater dieser Notation, Charles Simonyi, ein gebürtiger Ungar ist und die Namen auf den ersten Blick tatsächlich ein wenig fremdländisch wirken.
Ungarische Namen bestehen aus drei Teilen: Basistyp, Präfix und Bezeichner.
Basistypen bezeichnen den Datentyp der Variable. Dabei werden sowohl die Standardtypen als auch abstrakte Datentypen durch Kürzel repräsentiert. Einige übliche Kürzel für die Standardtypen:
| Basistyp | Bedeutung |
| n | allgemeiner numerischer Wert (int, oft auch UINT oder LONG). |
| u | numerischer Wert ohne Vorzeichen (UINT). Diese werden in der Praxis jedoch oft auch mit "n" bezeichnet. |
| l | LONG, wird oft auch mit "n" bezeichnet. |
| dw | DWORD |
| b | BOOL |
| f | float |
| f | steht in Einzelfällen auch für ein Flag (boolscher Wert). Flags sollten besser mit "b" gekennzeichnet werden. |
| ch | einzelnes Zeichen (char). |
| by | einzelnes Byte (BYTE). |
| sz | 0-terminierter String als Charakter-Array (char []). |
| s | String bei Verwendung einer Stringklasse (CString oder AnsiString). |
Bei abstrakten Typen ist es notwendig, sich selber sinnvolle Abkürzungen zu überlegen, da es hierfür keinen Standard gibt. Die folgende Liste enthält deshalb nur einige Beispiele:
| Basistyp | Bedeutung |
| wnd | Fenster |
| btn | Button (Kontrollelement, TButton) |
| txt | Edit-Feld (Kontrollelement, TMemo) |
| ev | Event (Win32 Kernelobjekt, CEvent) |
| cs | Struktur für die Absicherung eines kritischen Bereiches (CRITICAL_SECTION, CCriticalSection) |
| cs | Struktur für die Erzeugung eines Fensters (CREATESTRUCT) |
Festzustellen ist, daß die Abkürzung der Basistypen nicht immer eindeutig ist. Das liegt bei den Standardtypen daran, daß sie äußerst wenig über den Verwendungszweck der Variable aussagen. So ist es meistens nur von Interesse, daß eine Variable einen numerischen Typ hat ("n") und es ist unwesentlich, ob dieser nun ein int, LONG, UINT oder DWORD ist.
Bei den abstrakten Datentypen ist der Verwendungszweck meistens klar, hier liegt das Problem vor allem an der Natur von Abkürzungen. Zum Beispiel werden CRITICAL_SECTION und CREATESTRUCT beide mit "cs" abgekürzt. Dies ist zwar unschön, läßt sich jedoch, will man zu lange und/oder kryptische Bezeichnungen vermeiden, nicht verhindern. In der Praxis kommt es jedoch äußerst selten vor, daß zwei gleiche Abkürzungen im selben Abschnitt des Codes verwendet werden. Und kommt es doch einmal vor, so hilft meistens der eigentliche Name. Im folgenden Codeabschnitt dürfte wohl eindeutig sein, wofür das "cs" jeweils steht.
...
csGlobalsLock.Lock ();
CreateWindow (&g_csMainWnd, ...)
csGlobalsLock.Unlock ();
...
Präfixe gehen über den Basistyp hinaus und beschreiben den Zweck einer Variablen. Im Gegensatz zu den Basistypen sind Präfixe eindeutig. (Sie sollten es zumindestens sein.)
Die Präfixe sind meiner Meinung nach auch das wichtigste an der ungarischen Notation, da sie die Bedeutung einer Variablen im Programmkontext beschreiben. Oft bestehen ungarische Namen nur aus Präfix und Bezeichner:
| Präfix | Bedeutung |
| a | Array. |
| c | Zähler (Counter), z.B. Anzahl der Elemente eines Arrays. |
| d | Differenz zwischen zwei Variablen desselben Typs. |
| e | Element eines Arrays. |
| h | Handle (Kennziffer, Objekte des Betriebssystems werden häufig durch Handles representiert). |
| i | Index eines Arrayelementes. |
| p (lp, np) | Zeiger (Pointer). (Oft findet man auch noch die 16-Bit Relikte "lp" und "np" für Long Pointer und Near Pointer). |
| r (C++) | Referenz, wird in Parameterlisten für Variablen verwendet, die zurückgegeben werden sollen, also NICHT für konstante Referenzen. |
| g_ | globale Variable. |
| m_ (C++) | Membervariable. |
| tl_ | threadlokale Variable (Thread Local Storage). Threadlokale Variablen sind "global" innerhalb eines Threads. |
| pf | Funktion, die als Parameter übergeben wird (Callback-Funktion). Wird oft auch mit "pfn" bezeichnet. |
Präfixe erscheinen im Namen vor dem Basistyp. Sie können beliebig mit weiteren Präfixen oder Basistypen kombiniert werden. Präfix und Basistyp sind zusammen der kleingeschriebene Teil, mit dem jeder Variablenname beginnt. Hier einige Beispiele:
| Variablenanfang | Bedeutung |
| psz | Zeiger auf ein Charakter-Array (char []), das einen 0-terminierten String representiert. |
| ach | allgemeines Charakter-Array (char []), z.B. für einen Puffer |
| cch | Anzahl der Zeichen in einem Charakter-Array |
| ich | Index des gerade bearbeiteten Zeichens |
| ppsz | Zeiger auf einen Zeiger auf einen String |
| apsz | Array von Zeigern auf Strings |
| g_sz | Globaler String |
| hwnd | Handle eines Fensters |
Die ungarische Notation hebt ausdrücklich den semantischen Unterschied zwischen Arrays und Zeigern hervor. Diese werden in C ja bekannterweise syntaktisch gleich behandelt, was häufig zu Verwirrungen führt.
Der dritte Anteil eines ungarischen Namens ist der Bezeichner. Außerhalb der ungarischen Notation bestehen Variablennamen meistens nur aus dem Bezeichner. Der Bezeichner ist der eigentliche Name, aus dem der Sinn und Zweck der Variablen im Kontext des Codes hervorgehen sollte. (Stichwort "Sinnvolle Variablennamen")
Unter Windows ist es üblich, Bezeichner für Variablen (und Funktionen) mit einem Großbuchstaben zu beginnen und durch Groß-/Kleinschreibung zu unterteilen.
Zusätzlich zu den selbst vergebenen Bezeichnern kennt die ungarische Notation einige Standardbezeichner:
| Bezeichner | Bedeutung |
| Min | Absolutes, erstes Element
eines Arrays oder einer Liste. Untere Grenze eines Wertebereiches. |
| Max | Absolutes, letztes Element
eines Arrays. Obere Grenze eines Wertebereiches. |
| First | Das erste relevante Element eines Arrays bezogen auf eine bestimmte Operation. |
| Last | Letztes relevantes Element eines Arrays. Gegenstück zu "First". |
| Lim | Erster Index, der nicht mehr gültig ist, bezogen auf eine bestimmte Operation. Generell gilt Lim = Last + 1. |
Beispiele für die ungarische Notation
Die folgenden Quelltextbeispiele sollen die ungarische Notation nochmal verdeutlichen:
BOOL GetCommandLine (char* pszDest, int cchDest);
BOOL SplitCommandLine (const char* pszCommandLine,
const char* rapszArgs[], int cpszArgs);
BOOL GetScreenContext (SCREEN_CONTEXT& rscDest);
BOOL SetScreenContext (const SCREEN_CONTEXT& scNew);
BOOL EnumWindows (PFENUMWND pfEnumWndCB);
| Bezeichner | Bedeutung |
| pszDest | Zeiger auf einen Puffer, der mit dem Ergebnis gefüllt wird. |
| cchDest | Größe des Puffers in Zeichen. |
| pszCommandLine | Zeiger auf den String, der von GetCommandLine () ermittelt wurde. |
| rapszArgs[] | (Referenz) eines Arrays von Zeigern auf konstante Strings, in denen die Adressen der Strings der einzelnen Argumente zurückgegeben werden. (OK, dieses Beispiel ist wirklich heavy.) |
| cpszArgs | Größe des Arrays. |
| rscDest | Referenz auf einen SCREEN_CONTEXT, der das Ergebnis erhalten soll. |
| scNew | SCREEN_CONTEXT, der gesetzt werden soll. Dieser ist kein Rückgabewert und wird nur aus Speicherplatzgründen als Referenz übergeben, was an dem "const" leicht zu erkennen ist. |
| pfEnumWndCB | Callbackfunktion für die Auflistung von Fenstern |
| g_achArgs | globaler Puffer zum Speichern von Argumenten |
| g_hwndMain | globales Handle des Hauptfensters |
| m_btnStart | Membervariable für den Startknopf in einem Dialog. |
| chAct | aktuelles Zeichen |
| nInstances | Anzahl der Instanzen |
| bSuccess | Ergebniswert einer boolschen Funktion |
| ichFirst | Index des ersten zu bearbeiten Zeichen eines Arrays oder Strings. |
Vor- und Nachteile der ungarischen Notation
Die ungarische Notation bietet, hat man sich erstmal an diese Schreibweise gewohnt, eine Menge Vorteile. Besonders betonen möchte ich hier die Verwendung der Präfixe, die einem eine Menge über die Semantik einer Variable verraten. So rufen einem beispielsweise folgende Codezeilen schon von weitem zu: "Ich bin ein Fehler".
pcsMainWnd.nMinWidth = 4711;
dwData[47] = 11;
for (int i = 0; i < fAnz, i++)
...
Auch wenn man sich mit dem Abkürzen der Basistypen nicht so recht anfreunden kann, die Verwendung der grundlegenden Präfixe wie p, a, g_ erleichtert das Verständnis des Codes ungemein.
Des weiteren bietet die ungarische Notation präzise Namen gerade an den Stellen, die sonst häufig uneinheitlich benannt werden. Gerade hier sind die Standardbezeichner Min, Max, First, Last und Lim sehr nützlich.
Ein Nachteil der ungarischen Notation ist, das sie oft zu uninformativen Variablennamen verleitet. Häufig wird der Bezeichner-Teil komplett weggelassen und nur noch die Typbeschreibung verwendet. Ein Fenster heißt dann hwnd, ein String psz usw. Diese Namen sagen dann natürlich nichts mehr über den Verwendungszweck aus. Was für ein Fenster meint hwnd? Ein Eingabefenster, das Hauptfenster oder ein Kontrollelement? Sicher, es gibt Situationen in denen es wirklich allgemein nur um irgend ein beliebiges Fenster geht, aber dieses kommt doch eher selten vor.
Ein weiterer Nachteil besteht darin, das sehr viele verschiedene Versionen der ungarischen Notation im Umlauf sind, die sich geringfügig voneinander unterscheiden und mehr oder weniger streng gehandhabt werden. Allen gemein ist aber die Verwendung der Präfixe, die, wie gesagt, den meiner Meinung nach wichtigsten Teil dieser Notation darstellen.
Ungarische Notation für Funktionen
Manchmal sieht man die ungarische Notation auch bei Funktionsnamen. Diese bekommen dann meistens das Präfix fn (für Funktion) vorangestellt. Dies ist redundant, da Funktionen so oder so an den Klammern um die Parameterliste zu erkennen sind. Sinnvoller wäre es, der Funktion ihren Typ voranzustellen, also psz für eine Funktion die einen String zurück gibt usw. Letztendlich ist diese Notierung von Funktionen unter Windows aber schlicht unüblich.
Klassen sind die mit Abstand leistungsfähigsten und komplexesten Gebilde von C++. Gerade im Umgang mit Klassen sind Konventionen sinnvoll, um das Verständnis zu erleichtern. Ich schlage deshalb folgene Regeln für die Namensgebung und -verwendung in Klassen vor:
Das folgende Beispiel soll oben gesagtes noch einmal verdeutlichen:
class CTrashObj : public CNodeA
{
public:
CTrashObj (int x);
int m_x;
int m_nSizeCPO;
DWORD m_dwCreationTime; // time stamp of creation
int m_nId; // internal identification number
private:
// class variables
static int nNextId;
};
inline CTrashObj::CTrashObj (
int x
)
{
...
m_nId = CTrashObj::nNextId++;
::printf ("Created object %d!", m_nId);
}
(Daniel Lohmann, Mai 1997)
Steve McConnell, "Code complete - Das praktische Handbuch zur strukturierten Software-Entwicklung", Microsoft Press 1993
Charles Petzold, "Programming Windows 3.1", Microsoft Press 1992
Jocelyn Garner, "Microsoft Foundation Class Library Development Guidelines", MSDN August 1995