Seminar Programmierstil:
Codierungsstandards
von Kurt Prünner

Inhaltsverzeichnis

1    Codierungsstandards
2    Allgemeine Stilkriterien
3    Namensgebung
3.1    Allgemein
3.2    Hungarian Notation
4    Whitespace
5    Einrückungen
6    Klammerung
7    Literatur

1 Codierungsstandards

Ein Codierungsstandard ist eine Menge von Regeln, deren Einhaltung dafür sorgen soll, daß Code les- und wartbar bleibt, vor allem...

(Fast) jeder Codierungsstandard ist besser als gar keiner, und es ist sehr oft das kleinere Übel, sich von Anfang an an einen vorgegebenen Codierungsstandard zu halten, als größere Mengen Code nachträglich an diesen anpassen zu müssen.

"Den Einen" Codierungsstandard gibt es nicht - es ist daher oft erforderlich, sich an einen anderen, oft schon von außen vorgegebenen Standard anzupassen.

Die folgenden Kapitel sollen daher hauptsächlich als Denkanstoß verstanden werden und nicht als ein in Stein gemeisselter Codierungsstandard.

2 Allgemeine Stilkriterien

Schreibe Programme, die andere Leute lesen können, nicht nur der Compiler. Früher, als optimierende Compiler noch wenig ausgereift waren, gab es oft die Möglichkeit, ein wenig mehr Performance aus einem Rechner herauszukitzeln, indem man den Code von Hand unter Verwendung von mehr oder weniger kryptischen Sprachkonstrukten optimierte, was aber oft zu deutlichen Verständnisproblemen geführt hat, wenn man anhand des Codes nachvollziehen wollte, welcher Algorithmus hier eigentlich implementiert wurde.

So nicht:

#include <stdio.h>
#define PO(o,t)\
(((o>64)&&(o<91))?(((t>96)&&(t<123))?(t-32):(t)):(((t>64)&&(t<91))?(t+32):(t)))

      void main() {                                       char *poo= "poot",
      *Poo="pootpoot"      ,O[9];int      o,t,T,p;(t=p   =0)||(*O='\0');while
      ((o=       getc(   stdin   ))!=(   EOF))if  ((p==   0)&& (((o>64 )&& (
      o<91       )) ||   ((o>     96 )   &&(o<     123)        ))) (
      t!=8       )&&(O   [t]=     o)&&   (O[++     t] =        '\0')
      ;else {if (t>7)    {for     (T =   0 ; T     <=7;        T++ )
      printf("%c",       PO(*(   O+T),   *(Poo+   T)));       printf
      ("%c",              o);}else if     (t>3){for (T        =0;T<=
      3;T++)                                                  printf
      ("%c",                                                  PO(*(O
      +T),*(                                                  poo+T)

) ) ; printf( "%c" , o ) ; } else  printf ( "%s%c" , O , o )  ; ( t =  0 ) || (
* O =  '\0' ) ; ( o == 60 ) && ( ++p ) ; ( o == 62 ) && (p!=0) && ( --p ) ; } }

(http://ioccc.org/1998/dlowe.c)

Brich Zeilen um, die eine gewisse Länge (oft 80 oder 100 Zeichen) überschreiten. Nichts macht ein Listing unlesbarer als von einem Editor an den unnötigsten Stellen automatisch umgebrochene oder (noch schlimmer) abgeschnittene Zeilen. Exzessive Einrückung (siehe später) tut da ein übriges dazu.

So nicht:

class Foo
{
        Object lock=new Object();

        boolean checkBar()
        {
                synchronized(lock)
                {
                        if ((x == 7) && (y <= 10) &&
(z > 13))
                        {
                                updateBar(x+1, y+4,
z-2);
                                return true;
                        }
                        else if ((x == 7) && (y <=
10) && (z > 13))
                        {
                                rollbackBar()
                                return false;
                        }
                }
        }
}

Ersetze "Magische Zahlen" durch Konstanten. Fast alle in einem Programm vorkommende Zahlen (von 0 und 1 einmal abgesehen) werden leichter verständlich, wenn man sie durch eine Konstante mit sprechendem Namen ersetzt.

So nicht:

if ((ch == 37) || (ch == 42) || (ch == 47))

Besser so:

if ((ch == '%') || (ch == '*') || (ch == '/'))

Zerlege komplexe Ausdrücke in kleinere Teilausdrücke, um diese besser verstehen zu können. Auch die Definition von neuen Variablen für Zwischenergebnisse (für die der Compiler sowieso Werte auf dem Stack hätte zwischenspeichern müssen) macht den Code oft leichter verständlich.

So nicht:

*x += (*xp=(2*k < (n-m) ? c[k+1] : d[k--]));

Besser so:

if (2*k < (n-m))
  *xp = c[k+1];
else
  *xp = d[k--];

*x += *xp;

3 Namensgebung

3.1 Allgemein

Wähle sprechende Namen. Die Zeiten, in denen Variablennamen auf höchstens 8 Zeichen beschränkt waren, sind schon länger vorbei.

Achte auf eine konsistente Namensgebung. Vor allem bei Abkürzungen von längeren Worten bzw. Wortbestandteilen ist es wichtig, sich auf eine gleichbleibende Abkürzung zu einigen, um unnötige Verwirrung zu verhindern.

So nicht:

class UserQueue
{
  int noOfItemsInQ, frontOfTheQueue, queueCapacity;
  public int noOfUsersInQueue() {...}
}

Wähle passende Namen. Vor allem bei Funktionen ist es wichtig, am Namen der Funktion in etwa erkennen zu können, was darin passiert, damit man nicht bei jedem Aufruf erst lange suchen oder grübeln muß, was denn darin nun genau passiert. Noch schlimmer ist es, wenn der Name gar nichts mit der Funktion zu tun hat oder das genaue Gegenteil suggeriert.

So nicht:

boolean isDigit(char i)
{
  return (i == ' ');
}

boolean checkDigit(char i) { ... }

int hansi(float f) { ... }

3.2 Hungarian Notation

Bei der Hungarian Notation handelt es sich um eine von einem Ungarn, nämlich Charles Simonyi, entwickelte Namenskonvention, die vor allem im Zusammenhang mit C und Microsoft Windows verwendet wird.

Die Hungarian Notation soll sicherstellen, daß Namen für gleiche Objekte oder Aufgaben immer gleich heissen, damit es weniger Namen gibt, die man sich merken muß. Weiters soll sie es auch ermöglichen, daß gewisse Typ-Fehler schon aus dem Namen der verwendeten Variable ersichtlich sind - cpaReformat[i] wäre z.B. ein Fehler, da cpaReformat laut Hungarian Notation ein Zähler und keine Array ist. (Anmerkung des Autors: für mich persönlich sieht Hungarian Notation einfach nur häßlich aus und ich hoffe, daß sie außerhalb von Windows keine größere Verbreitung gefunden hat.)

Ein Name in Hungarian Notation Name besteht aus 3 Teilen, einem oder mehreren Prefixes, einem Base Type und einem Qualifier:

Prefixes:

a Array
c Count
d Difference
e Array-Element
g Global
h Handle
i Array-Index
m Modul-global
p Pointer

Base Type:

Programmspezifisch, z.B. wn (Window), scr (Screen), ch (Character), ...

Qualifier:

Beschreibender Teil des Namens, z.B. pwnMain, ghscrUserWorkspace, chRead, ...

Standard-Qualifier:

Min  Array-Anfang
First Erstes zu bearbeitendes Element
Last  Letztes zu bearbeitendes Element
Lim  Oberes Limit, das nicht erreicht wird (Last+1)
Max  Array-Ende

4 Whitespace

Verwende Leerzeilen, um zusammengehörige Codezeilen von anderen abzutrennen. Programmteile, die durch Leerzeilen in einzelne Blöcke (z.B. Blöcke von Zuweisungen an Felder eines Objekts, Blöcke von aufeinanderfolgenden Zuweisungen oder Methodenaufrufen) gruppiert werden, kann man schneller unterscheiden, als wenn stur eine Zeile auf die andere folgt.

Beispiel:

StringBuffer buf = new StringBuffer();
int i = 0;
int j;

while ((j = st.indexOf(s,i)) >= 0)
{
  buf.append(st.substring(i,j));
  buf.append(r);

  i = j+s.length();
}

Verwende Leerzeichen, um Operanden und Operatoren in Ausdrücken zu gruppieren und ihre Lesbarkeit zu erhöhen. Dabei ist es allerdings auch wichtig, die Vorrangregeln zu beachten und nicht durch die Leerzeichen eine andere Operatorpriorität zu suggerieren.

So nicht:

x = y+4 * z-3

Besser so:

x = y + 4*z - 3

5 Einrückungen

Einrückungen sind wohl das wirksamste Mittel, um logische Blöcke im Programmcode optisch hervorzuheben. Dies allerdings nur so lange, wie die Einrückungen und die wirklichen Blöcke zusammenpassen, also Vorsicht bei Änderungen!

Einrückungen sollen die logische Struktur des Codes genau und konsistent wiedergeben. Es kann leicht passieren, dass man mehr auf die Einrückungen achtet als auf die tatsächliche logische Struktur des Codes, sodaß man Fehler wie in dem ersten folgenden Beispiel leicht übersehen kann.

So nicht:

for (int i = 1; i < 10; i++)
  score[i] = 0;
  name[i] = "";
  date[i] = null;

if (i != 0)
i--;
else
  return i;

Einrückungen sollen den Code lesbarer machen. Daher ist es wichtig, mit den Einrückungen nicht am rechten Seitenrand "anzustoßen" und die Einrückungstiefe konstant zu halten.

So nicht:

if (x == 0)
    if (y == 0)
            x++;
else
  y--;
    else
        x++;

Einrückungen sollen Änderungen gegenüber robust sein. So schön eingerückter Code manchmal auch aussehen mag - wenn schon kleine Änderungen größere Neuformatierungen nach sich ziehen (weil z.B. händisch jede Menge Leerzeichen eingefügt oder gelöscht werden müssen) ist der dadurch verursachte Aufwand wohl größer als der praktische Nutzen.

So nicht:

void doSomething(String firstName,
                 String lastName,
                 String company)

6 Klammerung

Klammerung soll Ausdrücke für den, der den Code liest, eindeutig machen.

Setze bei komplizierteren Ausdrücken auch dann Klammern, wenn es durch die Vorrangregeln der Programmiersprache nicht unbedingt nötig wäre. Vor allem, weil man nicht immer alle für eine Programmiersprache gültigen Vorrangregeln im Kopf haben kann (weder derjenige, der den Code geschrieben hat, noch derjenige, der ihn liest) ist es hilfreich, durch Klammerung die Auswertungsreihenfolge eindeutig festzulegen.

So nicht:

leapYear = y%4 == 0 && y%100 != 0 || y%400 == 0;

Besser so:

leapYear = ((y%4 == 0) && (y%100 != 0)) || (y%400 == 0);

Vor allem bei der Verwendung von Operatoren, die nicht in direktem Zusammenhang stehen, ist es sinnvoll, auf jeden Fall Klammern zu setzen.

Beispiel:

if (x&MASK == BITS)

Das bedeutet in Wahrheit:

if (x & (MASK == BITS))

Gemeint war aber wohl:

if ((x & MASK) == BITS))

7 Literatur

This Page Is Valid HTML 4.01 Strict!