DIGITAL
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.
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 euler_angles {
/// 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 compare_angles(euler_angles a, euler_angles b, radian threshold) {
// ...
}
Anmerkungen:
euler_angles
freiwillig (aber trotzdem zu empfehlen). ///
(wie hier) benutzen, und diese einer Deklaration eines Typs, Funktion oder Member voranstellen, dann erkennen spezielle Tools wie Doxygen dies als "Dokumentationskommentare" und können automatisch eine Quelltextdokumentation erkennen. Laut C++-Sprachstandard sind nur zwei Schrägstriche nötig (//
) – solche Kommentare werden nicht in die Dokumentation mit aufgenommen.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).
Wir empfehlen, Bezeichner ("Identifier", also Namen, die Sie für Typen, Funktionen und Variablen vergeben) einheitlich zu gestalten.
Wir schlagen den folgenden Stil vor, konsistent mit der C++-Standartbibliothek
my_first_variable
, my_second_variable
. Den gleichen Stil benutzt man für Parameter, Funktionen, und Klassenmember (Funktionen und Variablen.)my_first_class
const int MY_UNIVERSAL_CONSTANT = 42
oder #define MY_SUSPICIOUS_CONSTANT = 23
.Sie sollten Lehrzeichen zwischen Operatoren, Klammern und ähnlichem konsistent verwenden (also z.B. immer a+b
oder immer a + b
schreiben). Als Orientierung können hier auch die Regeln für Python (siehe PEP 8) dienen.
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).
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;
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 *my_buffer = new byte[42];
// wir füllen den mit irgendwelchen Daten (nicht genauer spezifiziert)
fill_buffer_with_stuff(my_buffer);
// und nun legen wir einen zweiten an
Byte *my_other_buffer = 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 my_buffer in my_other_buffer Kopieren.
Byte *p = my_other_buffer;
while (p!=my_other_buffer+42) *p++ = *my_buffer++;
Variante 2: Länger, aber leicht lesbar
// jetzt möchten wir den Inhalt von my_buffer in my_other_buffer Kopieren.
Byte *sourcePtr = my_buffer;
Byte *destPtr = my_other_buffer;
Byte endPtr = sourcePtr+42;
while (sourcePtr != endPtr) {
*destPtr = *sourcePtr
destPtr++;
sourcePtr++;
}
Variante 3: Oder noch besser
// jetzt möchten wir den Inhalt von my_buffer in my_other_buffer Kopieren.
for (int counter=0; counter<42; counter++) {
my_buffer[counter] = my_other_buffer[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.
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.