5. Programmierstile

5.1. Einführung

Die Programmiersprache C wurde von Programmierern für Programmierer entwickelt. Sie lässt dem Benutzer der Sprache große Freiheiten in der Realisierung von Algorithmen. Konkret bedeutet dies auch, dass man C-Programme sehr klar und übersichtlich darstellen kann. Viele Programmierer sagen, dass sich ein gut formuliertes Programm bzw. eine Funktion "wie ein Buch" lesen lassen sollte. Diese Forderung lässt sich allerdings nur dann erfüllen, wenn der Programmierer sehr sorgfältig und vor allem diszipliniert arbeitet.

Bedauerlicherweise sieht die Praxis in vielen Fällen ganz anders aus. Es existieren C-Programme und -Funktionen, die bei vielen Magenkrämpfe und Augenzucken hervorrufen. Solche Programme stammen von Personen, die man gut und gerne als "Kraut und Rüben"-Programmierer bezeichnen kann. Vielleicht werden Sie jetzt sagen: "Die Hauptsache ist, dass der Programmierer, der die Funktion geschrieben hat, weiß, was sie tut". Da haben Sie schon - mit Einschränkungen! - recht, aber nach wenigen Wochen oder sogar schon nach Tagen kann der Programmierer meistens seinen eigenen Gedankengang zum Zeitpunkt der Programmentwicklung nicht mehr nachvollziehen. Er steht spätestens dann genau vor dem gleichen Problem, mit dem auch alle anderen zu kämpfen haben: Er muss sich neu einarbeiten und sich mit dem Programm auseinandersetzen.

Ferner ist es denkbar, dass der Verfasser eines Programmes und derjenige, der daran Änderungen vornehmen soll, nicht ein und dieselbe Person ist.

Ein Großteil der Literatur geht auf dieses Thema kaum oder überhaupt nicht ein. Dabei ist ein vernünftiger Programmierstil eine elementare Voraussetzung für professionelles Programmieren.

5.2. Neun hilfreiche Regeln

Die folgenden Regeln sind Möglichkeiten, die die Übersichtlichkeit und Lesbarkeit eines C-Programms steigern.

Regel 1:
Gehen Sie nicht zu sparsam mit dem Einfügen von Leerzeilen um. Beispielsweise können Sie in einer Funktion den Vereinbarungsteil (Deklaration von Variablen usw.) vom Anweisungsteil auch optisch mittels einer oder mehrerer Leerzeilen voneinander trennen.

Regel 2:
Rücken Sie logisch zusammengehörige Einheiten um einige Positionen ein (gewöhnlicherweise um 3 oder 4 Leerzeichen; bei vielen Editoren kann auch der Tabulator für die Einrückung verwendet werden). Somit können Sie die Verschachtlungstiefe von Anweisungsblöcken auch optisch darstellen.

Regel 3:
Fügen Sie innerhalb von Anweisungen an entsprechenden Stellen Leerzeichen ein. Das bietet sich besonders bei Ausdrücken an - z.B. zwischen den Variablen, den Konstanten und den Operatoren. So ist die Schreibweise

ergebnis=a*x*x+b*x+c;

nicht annähernd so übersichtlich wie

ergebnis = a * x * x + b * x + c;


Regel 4:
Wählen Sie für Variablen, Funktionen, Sprungmarken, usw. immer aussagekräftige Namen. Das bedeutet zwar in den meisten Fällen einen höheren Aufwand an Schreibarbeit, aber die Bezeichner des Ausdrucks

umfang_kreis = 2 * radius * pi;

sind selbstdokumentierend und wesentlich verständlicher als die der Anweisung

u = 2 * r * p;


Regel 5:
"Mysteriöse" und "Nichtssagende" Zahlen und Zeichen sollten Sie durch symbolische Konstanten ersetzen. Es versteht sich von selbst, dass Sie dafür wieder aussagekräftige Bezeichner wählen sollten.

Beispiel:
C bietet eine Reihe von Operatoren, deren Schreibweisen nicht immer leicht zu merken sind. Es steht Ihnen frei, für diese Operatoren aussagekräftige Namen zu vereinbaren und mit diesen in Ihrem Programm zu arbeiten.

   #define AND  &&  /* logische UND-Verknuepfung           */
   #define OR   ||  /* logische ODER-Verknuepfung          */
   #define NOT  !   /* logische Negation                   */
   #define BAND &   /* bitweise UND-Verknuepfung           */
   #define BOR  |   /* bitweise ODER-Verknuepfung          */
   #define BNOT ~   /* bitweise Negation (Bit-Komplement)  */
   #define BXOR ^   /* bitweise Exklusiv-ODER-Verknuepfung */
   #define SHR  >>  /* Schieben der Bitfolge nach rechts   */
   #define SHL  <<  /* Schieben der Bitfolge nach links    */
   #define MOD  %   /* Ganzzahliger Rest (Modulo)          */


Regel 6:
Kommentieren Sie Stellen in Ihrem Programm, die etwas komplizierter sind. Arbeiten Sie nach dem Motto: "Lieber etwas mehr Kommentar als zu wenig!"

Zum Einen sollten Sie beim Kommentieren vor Anweisungsblöcken eine einzeilige Kommentarzeile als Überschrift schreiben. So braucht man bei der Fehlersuche oder bei Änderungen nicht den Anweisungsblock analysieren, sondern findet anhand dieser Überschriften den gesuchten Anweisungsblock schneller. Zum Anderen sollten kompliziertere Zeilen am Zeilenende kommentiert werden. Hier bietet es sich an, diese Kommentare nicht gleich hinter dem Anweisungsende, sondern immer in der gleichen Spalte zu beginnen. Somit kann direkt in dieser "Kommentarspalte" das Programm gelesen werden.

Regel 7:
Jedes zu lösende Problem sollte zu Beginn gründlich analysiert und (soweit sinnvoll) in Unterprobleme aufgegliedert werden. Auch Unterprobleme können bzw. müssen in weitere Unterprobleme unterteilt werden. Eine C-Funktion stellt die Lösung eines Unterproblems dar. Die Funktionen, die zu einem Programm gehören, sind auf einem oder mehreren C-Modulen zu verteilen. Jede Funktion lässt sich damit einzelnd auf korrekte Funktionalität prüfen. Diese Arbeitsweise erleichtert auch die Fehlersuche und -behebung.

Regel 8:
Legen Sie zu jeder Funktion auch explizit deren Geltungsbereich und Ergebnistyp fest.

Regel 9:
Vermeiden Sie den Einsatz von Sprunganweisungen (goto)!

5.3. Funktionsheader

Jeder selbstdefinierten Funktion sollte einen sogenannten Funktionsheader vorangestellt bekommen, der relevante Informationen zur Funktion enthält. Er hat dokumentierenden Charakter und soll die wesentlichen Eigenschaften der Funktion knapp und übersichtlich darstellen. Der Header ist ein Mittel zur einheitlichen Dokumentation von Programmen. Ein Header für eine C-Funktion könnte das folgende Aussehen haben. Hierbei handelt es sich um einen leeren Header, also um eine Art Rahmen, der für jede Funktion individuell ausgefüllt werden muss.

kap05_01.c

01 /****************************************************************
02  * FUNKTION:
03  *---------------------------------------------------------------
04  * BESCHREIBUNG:
05  * GELTUNGSBEREICH:
06  * PARAMETER:
07  * ERGEBNISTYP:
08  * ERGEBNISWERTE:
09  *   -> NORMALFALL:
10  *   -> FEHLERFALL:
11  *---------------------------------------------------------------
12  * ERSTELLT VON:
13  *           AM:
14  * AENDERUNGEN :
15  ****************************************************************/

Nach dem Wort FUNKTION steht der Funktionsname. In den nächsten Zeilen folgt eine kurze BESCHREIBUNG der Aufgabe, die die Funktion erledigt. Für gewöhnlich reichen dazu schon wenige Sätze bzw. Stichpunkte aus. Dies soll eine Ist-Beschreibung und keine Soll-Beschreibung sein! Der GELTUNGSBEREICH enthält Informationen darüber, ob die Funktion nur im eigenen Modul (also lokal) oder auch in anderen Modulen (global) verwendet werden kann. PARAMETER liefert Informationen über die Parameter der Funktion. Dabei werden die Namen der Parameter, deren Bedeutung sowie die Art der Übergabe (per Kopie, per Zeiger oder - nur in C++ - per Referenz) angegeben. ERGEBNISTYP legt den Datentyp des Rückgabewertes der Funktion fest. Falls die Funktion keinen Wert mittels der Anweisung return zurückliefert, muss dort void stehen. Die Angabe der ERGEBNISWERTE wird noch unterteilt in NORMALFALL und FEHLERFALL. So kann z.B. eine Funktion, die eine Datei öffnen soll, den Wert 1 im NORMALFALL (d.h. die Datei wurde geöffnet) und den Wert 0 im FEHLERFALL (d.h. die Datei wurde wurde nicht geöffnet) zurückgeben. Gegebenenfalls muss noch die Bedeutung des Ergebnisses angegeben werden. ERSTELLT VON und AM gibt den Namen des Programmierers und das Erstellungsdatum an. Hinter AENDERUNGEN stehen alle Änderungen, die nach der ersten Erstellung der Funktion durchgeführt wurden - einschließlich Namen des Programmierers und Änderungsdatum. Dadurch entsteht im Laufe der Zeit die "Geschichte" (History) der Funktion.

Die - für viele sehr aufwändige - Gestaltung des Funktionsheaders mit den vielen Sternchen dient zum schnelleren Auffinden der Funktionen innerhalb des Programmcodes, da ein kompletter Kasten immer sofort auffällt.

Der hier vorgestellte Funktionsheader entspricht keiner Norm und kann problemlos umgestellt, erweitert oder auch anders gestaltet werden. Nun folgt noch ein Beispiel für einen vollständig ausgefüllten Funktionsheader:

kap05_02.c

01 /****************************************************************
02  * FUNKTION:        file_open
03  *---------------------------------------------------------------
04  * BESCHREIBUNG:    Oeffnet die angegebene Datei im
05  *                  angegebenen Modus.
06  * GELTUNGSBEREICH: Global
07  * PARAMETER:       char *Dateiname
08  *                  char *Modus
09  * ERGEBNISTYP:     int
10  * ERGEBNISWERTE:
11  *   -> NORMALFALL: 1 (Datei wurde geoeffnet.)
12  *   -> FEHLERFALL: 0 (Datei konnte nicht geoeffnet werden.)
13  *---------------------------------------------------------------
14  * ERSTELLT VON:    G. Kempfer
15  *           AM:    16.02.2004
16  * AENDERUNGEN :    18.03.2005 GK: Anpassung des Ergebnistyps
17  ****************************************************************/

5.4. Modulheader

Analog zu den Funktionen sollte auch jedes C-Programm und -Modul einen eigenen Modulheader erhalten. Dieser enthält modulbezogene Informationen und steht am Anfang eines Moduls. Ein möglicher Modulheader (auch hier gibt es keine Norm) könnte Platz für die folgenden Informationen beinhalten:

kap05_03.c

01 /****************************************************************
02  ****************************************************************
03  *** PROGRAMM:
04  *** MODUL:
05  *** VERSION:
06  *** BESCHREIBUNG:
07  *** GLOBALE FKT.:
08  *** LOKALE FKT.:
09  ***-------------------------------------------------------------
10  *** ERSTELLT VON:
11  *** DATUM BEGINN:
12  ***         ENDE:
13  *** AENDERUNGEN :
14  ****************************************************************
15  ****************************************************************/

Hinter PROGRAMM steht der Name bzw. die Bezeichnung des Programms, zu dem das Modul gehört. Hinter MODUL steht der Modulname. Für gewöhnlich ist er mit dem Namen der Datei identisch, in dem der Quelltext abgespeichert ist. Als nächstes kommt die Versionsnummer (VERSION) und eine kurze BESCHREIBUNG. Anschließend werden die globalen und lokalen Funktionen aufgelistet (nur die Funktionsnamen). Der Name des Programmierers bzw. der Programmierer steht hinter ERSTELLT VON. Darunter kommt das Anfangs- und Enddatum der Modulentwicklung. Zuletzt werden bei den AENDERUNGEN die Änderungen und Erweiterungen des Moduls angegeben. Dazu gehören auch wieder Name des Programmierers und Änderungsdatum. Eine Änderung im Modul hat für gewöhnlich auch eine Änderung der Versionsnummer zur Folge.

Im Folgenden wird ein Beispiel für einen ausgefüllten Modulheader gezeigt:

kap05_04.c

01 /****************************************************************
02  ****************************************************************
03  *** PROGRAMM:     Verwaltung von Dateien
04  *** MODUL:        file.c
05  *** VERSION:      1.1
06  *** BESCHREIBUNG: stellt elementare Funktionen zum
07  ***               Arbeiten mit Dateien zur Verfuegung
08  *** GLOBALE FKT.: file_open
09  ***               file_close
10  ***               file_create
11  ***               file_delete
12  *** LOKALE FKT.:  file_exists
13  ***-------------------------------------------------------------
14  *** ERSTELLT VON: G. Kempfer
15  *** DATUM BEGINN: 16.02.2004
16  ***         ENDE: 17.02.2004
17  *** AENDERUNGEN : 18.03.2005 GK: file_open angepasst
18  ****************************************************************
19  ****************************************************************/



Voriges Kapitel: 4. Programmentwicklung