JGU Logo JGU Logo JGU Logo JGU Logo

Institut für Informatik

Michael WAND
Christian ALI MEHMETI-GÖPEL
Wintersemester 2020/21DIGITAL

Einführung in die Softwareentwicklung

Letzte Änderung : 16:06 Uhr, 26 October 2020






Coding Style

Anders als Python erlaubt die Sprache C++ eine sehr viel freiere Gestaltung des Programmtextes: Einrückungen (und allgemein Lehrzeichen) haben in C++ anders als in Python keine semantische Bedeutung (heißt: dem Compiler sind die völlig egal).


Damit man als Mensch den Code noch lesen kann, ist es aber wichtig, ihn so zu gestalten, dass man die Struktur verstehen kann. Dies trifft insbesondere für die Korrektoren/innen zu, die viele Übungsaufgaben korrigieren müssen, und deren Leben wir nicht zu schwer machen sollten. Daher gibt es in der Vorlesung Einführung in die Softwareentwicklung einen "Coding-Style", den man einhalten muss bzw. soll.


Vorgschriebene Regeln

Folgende Regeln sind verbindlich. Wenn der eigene Code diese nicht einhält, dann führt dies zu Punktabzug.


Regel 1: Blöcke müssen konsistent eingerückt werden. Dazu müssen konsistent entweder


Hier ein Beispiel:

int a=42;
while (a > -3) {
    if (a>0) {
        cout << "Der Wert ist positiv!\n";
        if (a==42) {
            cout << "War ja klar.\n";
        }
    }
    a--;
}
cout << "Ich bin fertig!\n";


Regel 2: In jeder Zeile darf nur eine größere Anweisung stehen. Größere Anweisungen sind alle Block-Anweisungen wie auch Anweisungen, die alleine stehen können.


Als Ausnahmen ist zulässig:


Einfache Bedingungen mit nur einer Folgeanweisung sind erlaubt:

if (a==42) cout << "Heureka!\n"; // Hier Anweisung direkt nach if


Die ausführlichere Version

if (a==42) {
    cout << "Heureka!\n"; // Eigener Block, abgesetzt
}


ist freiwillig (aber empfohlen).


Else-if Konstruktionen, bei denen der nächste if-Block sich direkt anschließt sind nicht nur erlaubt, sondern sogar empfohlen:

if (a==42) {
    cout << "Heureka!\n";
} else if (a==23) { // Hier gleich mit if weiter
    cout << "Verschwörung!\n";
}


Ausdrücke mit mehreren Seiteneffekten wie

cout << "a" << "b" << "c" << endl;


oder

int a,b,c;
a = b = c = 0;


oder

for (int i=0; i<100; i++) {
    // ...
}


oder Initialisierungen direkt im Anschluss an Deklarationen

int a = 0;


und ähnliche Fälle in eine Zeile zu schreiben ist natürlich ebenfalls ok.


Nicht gewünscht wäre dagegen:

int a=0; while (a<42) {cout << a; a--;}


Dies sollte man so schreiben:

int a=0;
while (a<42) {
    cout << a; a--;
}


Anmerkung: Es ist schwer, diese Regel formal exakt zu definieren. In Zweifelsfällen wird man daher keine Punkte bei Nichteinhaltung abziehen. Dennoch haben wir diese in die verbindlichen Regeln aufgenommen, da lange Ketten von Anweisungen die Lesbarkeit erheblich beeinträchtigen (und die Einrückungsregel unwirksam machen). Wenden Sie im Zweifelsfall immer den gesunden Menschenverstand an.


Wie rücke ich richtig ein? Für die Einrückung von Blöcken gibt es die Stil, öffnende Klammern in die gleiche Zeile oder in die nächste Zeile zu schreiben. Beides ist erlaubt.

int a=42;
while (a > -3) {
    if (a>0) {
        cout << "Der Wert ist positiv!\n";
        if (a==42) {
            cout << "War ja klar.\n";
        }
    }
    a--;
}


oder

int a=42;
while (a > -3) 
{
    if (a>0) 
    {
        cout << "Der Wert ist positiv!\n";
        if (a==42) 
        {
            cout << "War ja klar.\n";
        }
    }
    a--;
}


Sie sollten dies konsistent benutzen (also bei while/for, bei if, bei Funktionsdefinitionen, bei Klassen jeweils immer den gleichen Stil; Klassen anders als while zu handhaben, ist ok).


Regel 3: Kommentare. Jede neu deklarierte Funktion oder neu deklarierter Typ muss einen Kommentar erhalten (vorangestellt), in dem kurz erklärt wird, wozu das gut ist. Beispiel:

/// Winkel im Bogenmaß (2Pi)
typedef double Radian;

/// Winkel im Winkelmaß (360°)
typedef double Degree;

/// Eulerwinkel - d.h., Drehung um drei Achsen im 3D-Raum
struct EulerAngles {
   /// Drehung um x-Achse (dieser Kommentar wäre optinoal!)
   Radian alpha;
   /// Drehung um y-Achse (dieser Kommentar auch)
   Radian beta;
   /// Drehung um z-Achse (dieser auch, aber es ist schon gut zu wissen)
   Radian gamma;
}

/// Diese Funktion vergleicht zwei Drehungen und gibt wahr zurück,
/// wenn diese sich nur unwesentlich unterschieden
/// Der Parameter threshold gibt den Winkel im Bogenmaß an, mit dem man
/// die Drehungen bei einer Drehung um eine optimale Achse ineinander 
/// überführen kann, ab dem Gleichheit angenommen wird.
bool compareAngles(EulerAngles a, EulerAngles b, Radian threshold) {
    // ...
}


Anmerkungen:

Unverbindliche Vorschläge

Diese Regeln sind nicht verbindlich; wir empfehlen aber dringend, diese zu berücksichtigen, damit die Arbeit der Korrektoren/innen erleichtert wird (und Ihr Code lesbarer).


Schreibweise für Bezeichner

Wir empfehlen, Bezeichner ("Identifier", also Namen, die Sie für Typen, Funktionen und Variablen vergeben) einheitlich zu gestalten.


Wir schlagen zwei Stile vor:


Python-Stil (konsistent mit EiP)


Java-Stil (für JAVA standartisiert, aber auch in C++ häufig):


Die Vorlesungsfolien benutzen den Java-Stil für C++ (und den Python-Stil für Python).

Lehrzeichen in einer Zeile

Sie sollten Lehrzeichen zwischen Operatoren, Klammern und ähnlichem konsistent verwenden (also z.B. immer a+boder immer a + b schreiben). Als Orientierung können hier auch die Regeln für Python (siehe PEP 8) dienen.

(Natürliche) Sprache

Bezeichener: Wir empfehlen, alle Bezeichner im Programm in englischer Sprache zu benennen.


Kommentare: Bei größeren Projekten ist es auch sinnvoll, alle Kommentare auf Englisch zu verfassen, da man damit eher international Code austauschen kann (z.B. für das eigene Open-Source Projekt auf github oder einer ähnlichen Platform). Für Übungsaufgaben ist dies optional (da alle Korrektoren/innen deutsch sprechen).

Vollständige Klammerung

Wir empfehlen (zumindest für die Übungen) alle Blöcke vollständig zu klammern. Dann gibt es kein Dangeling-Else-Problem, und insgesamt wird der Code leichter lesbar. Beispiel:


if (a==42) {
    if (b==23) {
        cout << "Treffer!" << endl;
    } else {
        cout << "a gefällt mir!" << endl;
    }
}


ist zwar genauso gut zu verstehen wie

if (a==42) 
    if (b==23) 
        cout << "Treffer!" << endl;
    else
        cout << "a gefällt mir!" << endl;


aber hier ist besteht ein Risiko, die Einrückung falsch zu setzen (der Compiler sieht das anders; z.B. aus versehen das Else weiter nach links einzurücken). Außerdem ist die Wartung schwieriger; man muss zusätzliche Klammern hinzufügen, wenn man einen Befehl ergänzen möchte. Vergisst man dies, stimmt die Programmlogik nicht mehr (trotz hübscher Einrückung, die einen in die Irre führen könnte.)


Beispiel für ein Problem:

// Achtung: Das Programm macht überhaupt nicht das, was man denkt!
if (a==42) 
    if (b==23) 
        cout << "Treffer!" << endl;
    else
        cout << "a gefällt mir!" << endl;
        cout << "a hat einen wirklich schönen Wert!" << endl;

Lesbarkeit

Sie sollten beim Programmieren immer Lesbarkeit und Nachvollziehbarkeit höher gewichten als "Coolness" oder Eleganz, falls diese das Verständnis erschwert. Hier ist ein Beispiel. Wir legen einen Block von 42 Bytes an, den wir kopieren möchten:


// Dies nur, um den Code in dem Beispiel zu illustrieren
typedef uint8_t Byte;
// Original anlegen
Byte *myBuffer = new Byte[42];
// wir füllen den mit irgendwelchen Daten (nicht genauer spezifiziert)
fillBufferWithStuff(myBuffer);
// und nun legen wir einen zweiten an
Byte *myOtherBuffer = new Byte[42];

Um zum Beispiel einen Speicherblock zu kopieren, könnte man diese beiden Programmvarianten schreiben:


Variante 1: Kompakt und kompliziert

// jetzt möchten wir den Inhalt von myBuffer in myOtherBuffer Kopieren.
Byte *p = myOtherBuffer;
while (p!=myOtherBuffer+42) *p++ = *myBuffer++;

Variante 2: Länger, aber leicht lesbar

// jetzt möchten wir den Inhalt von myBuffer in myOtherBuffer Kopieren.
Byte *sourcePtr = myBuffer;
Byte *destPtr = myOtherBuffer;
Byte endPtr = sourcePtr+42;

while (sourcePtr != endPtr) {
    *destPtr = *sourcePtr
    destPtr++;
    sourcePtr++;
}

Variante 3: Oder noch besser

// jetzt möchten wir den Inhalt von myBuffer in myOtherBuffer Kopieren.
for (int counter=0; counter<42; counter++) {
    myBuffer[counter] = myOtherBuffer[counter];
}

Die Variant 2 ist leichter lesbar als Variante 1, auch wenn Sie mehr Platz benötigt. Variante 3 ist noch einfacher zu verstehen, benutzt aber eine Implementierung, die etwas aufwendiger ist (eine Zählervariable wird explizit verwaltet und auf die Zeiger jedesmal draufaddiert). Dies ist für moderne C++-Kompiler kein Problem – danke automatischer Optimierung sollte der Code genauso schnell wie in den ersten beiden Fällen sein. Daher ist dies hier die beste der drei Lösungen.


PS: Natürlich gibt es viel elegantere und trotzdem besser lesbare Lösungen mit Containern, Iteratoren, etc. Es geht nur um das Prinzip.

Grundregel

Man programmiert nicht nur für den Compiler, sondern auch für Menschen:



Entsprechend sollte man immer darauf achten, dass ein menschlicher Leser leicht verstehen kann, was hier passiert. Dabei geht gesunder Menschenverstand (und etwas guter Geschmack) über alles. Man hüte sich vor Cargo-Kult; es geht nicht darum, formale Regeln blind zu befolgen, sondern den Programmcode möglichst gut verständlich und nachvollziehbar zu gestalten. Coding-Styles helfen dabei, eine gemeinsame Ausdrucksweise zu entwickeln, die das Verständnis erleichtern. Sie sollten nicht dem Verständnis des Programms im Wege stehen.


Sollten Sie eine Situation finden, in der es einen guten Grund (in diesem Sinne) gibt, von den Richtlinien abzuweichen, dann schreiben Sie in Ihrem Code einen Kommentar mit einer Erklärung. Wenn die sinnvoll erscheint, werden Ihnen keine Punkte abgezogen. Wir gehen allerdings davon aus, dass die drei verbindlichen Regeln nur in Ausnahmefällen in dem beschriebenen Sinne unzweckmäßig sind.








Datenschutz     Impressum