![]() |
|
|||||
Sicherlich ist nicht sofort ersichtlich, was eine Zeile wie j = i-- + ++i + i++ – --i so alles leistet. Vorsicht ist alle mal gegeben, denn viele taktische Kommentare machen den Programmtext unleserlich. Wir sollten daher versuchen, viele Informationen im strategischen Kommentarblock unterzubringen und nur wirklich wichtige Stellen mit taktischen Kommentaren markieren. Der folgende Ausschnitt gibt Einblick in die Klasse BigInteger. Die Klasse gehört seit Java 1.1 zum API-Standard: // Arithmetic Operations Die gesamte Klasse zerfällt in mehrere Teile, wobei der obere Teil ein Auszug der arithmetischen Operationen ist. Die Gliederung der Datei in die verschiedenen Ebenen besteht natürlich nur im Quelltext und Sun wählte, um dies deutlich zu machen, die Zeilen-Kommentare. Alles, was also mit // beginnt, gliedert den Quelltext. Das heißt aber auch, dass normale, also taktische Kommentare, mit den herkömmlichen Zeichen abgegrenzt werden.
Dieser Kommentar ist besonders gut, und wir kommen später noch einmal darauf zu sprechen, denn er erklärt den else-Teil der Fallunterscheidung. Auf den ersten Blick ist also zu sehen, wann dieses Codesegment ausgeführt wird. 27.3.1 Bemerkungen über JavaDoc
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Gotcha Schlüsselwort | Beschreibung |
| :TODO: topic | Eine Programmstelle muss noch weiter bearbeitet werden. |
| :BUG: [bugid] topic | Ein bekannter Fehler sitzt hier. Dieser muss beschrieben werden und wenn möglich, mit einer Fehlernummer (Bug-ID) versehen werden. |
| :KLUDGE: | Der Programmcode ist nur schnell zusammengehackt und muss überarbeitet werden. Leider eine zu häufige Programmiertechnik, die dazu führt, dass das Programm nur unter gewissen Bedingungen richtig läuft. |
| :TRICKY: | Uns fiel etwas besonders cleveres ein und das sollten wir sagen. Denn wie sollten die anderen unseren Trick erkennen, ohne lange nachzudenken? |
| :WARNING: | Warnt vor etwas. Beispielsweise hoher Speicherverbrauch, Zugriffsprobleme bei Applets. |
| :COMPILER: | Baut um einen Compiler oder Bibliotheksfehler herum. |
Die einzelnen Symbole sollten die Ersten im Kommentar sein; anschließend sollte eine Zeile kurz das Problem erläutern. Bei der Entwicklung im Team sollten die Programmierer, die von ihnen gemachten Anmerkungen, durch ein Namenskürzel kenntlich machen. Ebenso ist das Datum zu vermerken (nach einer Zeit können Bemerkungen entfernt werden) und jeder, der den Quelltext geändert hat. So lässt sich insbesondere die Behebung von Fehlern dokumentieren.
// :TODO: ulliull 970702: give the program the last kick
Für die Namensgebung von Variablen ist die (nicht unbestrittene) ungarischen Notation bekannt, die auf Charles Simonyi zurückgeht. Sie gibt uns schon einen kleinen Einblick in die Probleme der Namensgebung.
Die ungarische Notation ist eine Schreibkonvention, die Variablen und Funktionen durch ein optionales Präfix, einer Datentypangabe und einen Identifikator kennzeichnet. Die Identifikation ist der eigentliche Variablenname. Das Präfix und die Datentypangabe werden meist durch ein bis zwei Zeichen gebildet und geben weitere Auskunft über die Variable, die normalerweise nicht im Namen kodiert ist, zum Beispiel, um welchen Datentyp es sich handelt. Einige Vorschläge: p für einen Zeiger (von engl. pointer), lp, hp, np für einen long/huge/near-Zeiger und h für einen handle. Da es in Java keine Zeiger gibt, können wir auf diese Art von Präfix leicht verzichten. Auch für die Datentypen gibt es einige Vorschläge: f: Wahrheitswert (von engl. flag), ch: Zeichen (engl. character), sz für eine abgeschlossene Zeichenkette (engl. string, zero terminated). fn Funktion, w Wort, l long, b byte, u für einen Typ ohne Vorzeichen (von engl. unsigned) und v für einen unbestimmten Datentyp (engl. void). Der Identifikator soll nun nach Simonyi mit Großbuchstaben beginnen und sich aus Worten zusammensetzen, die wiederum mit Großbuchstaben beginnen.
Die Idee von Charles Simonyi münzt auf der Programmiersprache C und ist nur in Ansätzen auf Java übertragbar. Zudem stellt sich die generelle Frage, ob solch eine Namensgebung gut und sinnvoll ist – und wenn ja, ob in der Form, wie sie Simonyi vorschlägt. Da wir in Java nicht mit Pointern arbeiten, sondern nur mit Referenzen (wobei auch der Name »Handle« passend ist), erübrigt sich dieses Präfix. Den Datentyp mit anzugeben, scheint auch nicht schön zu sein, da wir sowieso nur zwischen wenigen Datentypen auswählen können. Für die grobe Trennung von Primitiven, Objekten und Funktionen müssen wir uns etwas einfallen lassen. Aber für die Primitiven brauchen wir die Angabe des Datentyps nicht, da die Anwendung schon allein durch den Kontext gegeben ist. Wahrheitswerte dürfen nur die Werte true oder false annehmen, ein Integer, der als Wahrheitswert dient, muss in Java durch einen Cast deutlich gemacht werden. Generell macht uns der Cast deutlich, um welchen Datentyp es sich handelt. Im Großen und Ganzen scheint der Vorschlag nicht so richtig Sinn zu machen, es scheint aber notwendig zu sein, die Namen so zu wählen, dass eine grobe Unterscheidung möglich ist.
Der Bezeichner dient zur Identifizierung einer Variablen (somit auch einer Konstante), Funktion oder Klasse und muss innerhalb eines Geltungsbereiches eindeutig sein. In verschiedenen Geltungsbereichen können dagegen die gleichen Bezeichner verwendet werden:
| Namen dürfen nur aus bestimmten Zeichen gebildet werden. Es sind Buchstaben und Ziffern erlaubt und auf das Unterstreichungssymbol »_« sollte verzichtet werden. Ausnahmen bilden zusammengesetzte Wörter, die mit einem Großbuchstaben enden, wie beispielsweise currentIO_Stream. Wenn möglich, sollten aber Zeichenketten in Großbuchstaben aufgelöst werden. So sollte MyABCExam zu MyAbcExam werden, die Alternative MyABC_Exam ist nicht gut. |
| Von Sonderzeichen sollte Abstand genommen werden, auch wenn Java diese Zeichen zulässt, da sie im Unicode-Zeichensatz definiert sind. |
| Setzt sich ein Bezeichnername aus mehreren Wörtern zusammen, so werden diese zusammengeschrieben und die jeweiligen Anfangsbuchstaben groß gesetzt. (KaugummiGenerator, anzahlKaugummis). |
| Es dürfen keine Bezeichner gewählt werden, die sich nur in der Groß- bzw. Kleinschreibweise unterscheiden. Java achtet auf Groß- und Kleinschreibung, sodass jedoch xyz und XYZ für eine Variable und eine Konstante verwendet werden können. Wir sollten also nicht die Funktionen makeMyDay() und irgendwo die Boolean-Konstante MakeMyDay einsetzen,da es zu Verwirrungen führt, obwohl es wären also formal zulässig. |
| Namen mit einer Mischung aus Buchstaben und Ziffern können schwer zu lesen sein. So ist I0 zu leicht mit IO zu verwechseln, auch die Zeichenkette l1 mit der Ziffer 1 und dem Kleinbuchstabe l sehen auf manchen Druckern gleich aus und ebenso bei schlechten Zeichensätzen. |
| Die vom System verwendeten Namen von Methoden und Feldern sollten nicht, auch nicht abgewandelt, im eigenen Programm verwendet werden. Die Verwechslungsgefahr mit Funktionen, die etwas ganz anderes machen, ist damit geringer. |
| Unaussprechbare Namen dürfen nicht vergeben werden. Ein aussagekräftiger langer Namen ist besser als ein kurzer, dessen Aufbau sich nicht erkennen lässt. So ist resetPrinter ein besserer Name als rstprt. |
| Namen dürfen keine Abkürzungen enthalten, die nicht allgemein anerkannt sind. So ist der Bezeichner groupID sicherlich besser als grpID. |
| Bei Abkürzungen ist darauf zu achten, dass diese nicht missverstanden werden. Heißt termProcess() vielleicht »terminiere den Prozess«, oder besteht die Verbindung zu einem »Terminal-Prozess«? |
| Bezeichner von Variablen und Methoden beginnen mit kleinen Buchstaben (int myAbc; float downTheRiverside; boolean haveDreiFragezeichen). |
| Bezeichner von Klassen beginnen mit großen Anfangsbuchstaben (class OneTwoTree) |
| Bezeichner müssen »sprechend«, also selbsterklärend, gewählt werden. |
| Ausnahme bilden Schleifenzähler. Diese werden oft einbuchstabig gewählt, wie i, k. |
| Konstanten schreiben wir vollständig groß mit Unterstrichen an den Wortgrenzen. Damit vermeiden wir von vorne herein das Problem, auf eine Konstante versehentlich schreibend zugreifen zu wollen. |
| Die Klassen sollen so benannt werden, dass die Methoden in Referenz.Methodennamen einfach zu lesen sind. |
Lange und komplizierte Funktionsnamen sollten vermieden werden. Wir müssen uns jetzt daran erinnern, was bei langen Klassen- und Funktionsnamen passieren kann:
CD_Recorder.getTheOwnerOfPlayer().printNameAndAgeOfPerson()
Die anderen Regeln über sprechende Namen habe im Zweifelsfall aber Vorrang.
Speziell für Java gibt es noch ein paar weitere Faustregeln: Methodennamen beginnen mit Verben, Klassennamen sind Substantive und Schnittstellennamen bevorzugt Adjektive.
Individuelle stilistische Fragen beim Programmieren müssen in den Hintergrund gestellt werden, da es bei Quelltexten in erster Linie um die Lesbarkeit für die gesamte Programmiermannschaft geht. Daher sollte genügend Freiraum für die Programmzeilen geschaffen werden, die den Code deutlich hervorheben, aber diesen nicht durch ungeschickte Einrückung verdecken. Es kann von uns nicht verlangt werden, dass wir uns in die ungewöhnliche Einrückung von Fremden einarbeiten müssen; wir verlieren nur wertvolle Zeit dadurch, wenn wir uns erst in das ästhetische Befinden unseres Programm-Erstellers einarbeiten müssen. Die richtige Platzierung der Leerzeichen ist also nicht unerheblich. Es müssen immer gewöhnliche Leerzeichen anstatt Tabulatoren verwendet werden. Verschiedene Editoren interpretieren Tabulatorzeichen als unterschiedlich breite Einrückungen. Unser feinfühliges Styling des Texts darf nicht an den verschiedenen Darstellungen der Editoren scheitern. Mittlerweile füllen auch viele Texteditoren beim Druck auf die Tabulatortaste den Freiraum mit Leerzeichen auf. Unter UNIX können Tabulatoren mit dem Programm expand durch Leerzeichen ersetzt werden. Eine Tabulatorlänge, die später mit Leerzeichen aufgefüllt wird, sollte zwei, drei oder vier Zeichen betragen. Zwei Leerzeichen seien nahe gelegt. Damit geht der Programmcode nicht zu sehr in die Breite.
Die Zeilenlänge sollte 78 Zeichen nicht überschreiten.
Das Einrücken von Programmcode ist eine sehr individuelle Vorliebe und führt vielfach zu emotionalen Kontroversen, weil jeder meint, seine Art der Einrückung sei die Beste. Der Programmierstil ist Ausdruck der eigenen Ästhetik, des Wunschs nach schnellem Tippen, des Drucks der Auftraggeber oder eines Normstils. Es zeigt sich glücklicherweise, dass jeder durch seinen eigenen Quellcode am schnellsten navigieren kann. Nur wenn es der Text eines anderen ist, kommt neben der kompakten Formulierung der Programme die oft ungewohnte Art der Einrückung hinzu.
Es gibt einige Programme, die Quelltexte konform einrücken und das bekannteste für C ist »indent«. Es stützt sich auf drei Einrückungskonventionen, die sich mittlerweile in C beziehungsweise C++ etabliert haben (oder hatten). Auch wenn wir hier eigentlich von Java reden, riskieren wir einen Blick in die Entwicklung von C (selbst auf die Gefahr hin, dass wir verdorben werden):
| Der Programmierstil von Kernighan, Ritchie (1978) wird in einem weitreichenden Buch über C-Programmierung festgelegt. Heutzutage sehen die Programme antiquiert aus. |
| Harbison, Steele (1984) beschreiben den H&S-Standard, der ein erweiterter K&R-Standard ist. Die modernen C-Compiler kommen dem Standard gut nach, der insbesondere zwischen K&R und ANSI seinen Platz gefunden hat. |
| Das American National Standards Institute formte den ANSI-Standard, der gegenüber H&S noch Erweiterungen aufweist. Zum ersten Mal wird auch die Aufgabe des Präprozessors genauer spezifiziert. |
| Der C++-Standard beschreibt eine Obermenge von ANSI-C. Es gibt immer wieder Verzögerungen in der Standardisierung und somit existiert die Sprache nur als Beschreibung von AT&T. |
Der Blick auf die Entwicklung von C und C++ ist interessant, denn über einen langen Zeitraum lassen sich gut Daten über Programmierstile sammeln und auswerten. Wir profitieren also in den Java-Richtlinien von der Vorarbeit unseres Vorgängers C++.
Die geschweiften Klammern »{}«, die einen Block einschließen, sollten eine Zeile tiefer in der gleichen Spalte stehen. Hier ist mitunter ein Kompromiss zwischen Lesbarkeit und kurzem Code zu schließen. Im Prinzip haben wir mit der Klammer unter dem Schlüsselwort nur zwei Möglichkeiten (exemplarisch an if gezeigt). Eine Variante setzt die Klammer in eine einzelne Zeile. Die traditionelle UNIX-Schreibweise setzt die Klammer in dieselbe Zeile wie die erste Anweisung:
if ( Bed ) if( Bed ) 2 if ( Bed ) { 3 { ... { ... } ... } }
Innerhalb von Blöcken sollte jede Anweisung in einer eigenen Zeile stehen. Nur eng verwandte Operationen sollten sich eine Zeile teilen. Als Beispiel sei die case-Anweisung genannt.
Die Kontrollfluss-Möglichkeiten mit den Schlüsselwörtern if, else, while, for und do sollten von einem Block gefolgt werden, auch wenn dieser leer ist. Fügen wir dann Zeilen in den Schleifenrumpf ein, kommt es zu keinen semantischen Fehlern. Der Kommaoperator ist in Java schon aus den Anweisungen verschwunden und es kommt seltener zu Missverständnissen4 . Es wird empfohlen, while-Schleifen, die ihre Arbeit innerhalb der Bedingung erfüllen, nicht einfach mit einem Semikolon abzuschließen, wie
while ( Bedingung );
Besser ist dann schon Folgendes:
while ( Bedingung )
{
// Empty !
}
Auf jeden Fall sollte auch eine einzelne abhängige Anweisung, nicht in derselben Zeile wie die Bedingung stehen. Ein nicht nachzuahmendes Beispiel ist etwa
if ( presentCheese.isFullOfHoles() ) reduceHomelessnesOfMice();
Verbinden sich if-Abfragen mit einem else-Teil, könnte ein Block wie folgt aussehen:
if ( Bedingung ) // Kommentar wann ausgeführt
{
}
else if ( Bedingung ) // Kommentar wann noch ausgeführt
{
}
else // Kommentar für das was bleibt
{
}
Bei der Funktionsdeklaration sollten wir immer bemüht sein, die Signatur so vollständig wie möglich anzugeben. In Java wird immer ein Rückgabewert gefordert. Compilieren wir beispielsweise die Zeile
public tst() { }
so ist dies, ohne public natürlich, unter C (auch C++) völlig korrekt, da Funktionen ohne explizit angegebenen Rückgabewerte immer den Ergebnistyp int haben; unter Java erhalten wir aber die Fehlermeldung:
Invalid method declaration; return type required.
public tst()
^
Neben der Angabe des Rückgabewertes sollten wir aber auch auf die Einrückungen und den Einsatz von Leerzeichen achten. So ist zwischen Funktionsnamen und öffnender Klammer der Parameterliste, sowohl in der Deklaration als auch später im Gebrauch der Funktion, kein Leerzeichen zu setzen. Grundsätzlich sollten wir vermehrt Leerzeichen einsetzen, um Lesbarkeit zu schaffen. Nach der öffnenden Klammer ist ein Leerzeichen zu platzieren, ebenso nach jedem trennenden Komma und vor der schließenden Klammer.
Die Parameter sind, wenn es der Platz erlaubt, in dieselbe Zeile zu schreiben. Reicht der Raum nicht aus, weil zu viele Parameter übergeben werden, ist zunächst einmal zu überlegen, ob nicht ein Designfehler vorliegt und die Signatur ungeschickt gewählt ist. Arbeiten wir konsequent objektorientiert, erübrigen sich vielfach Argumente. Die meisten API-Funktion von Java besitzen nicht mehr als ein oder zwei Parameter. Gibt es gute Gründe für den Einsatz vieler Parameter, so schreiben wir jeden Parameter in eine eigene Zeile. Die Klammer wird hinter den letzten Parameternamen gesetzt.
Der Ergebnistyp sowie die Sichtbarkeitsstufe sollten zusammen mit dem Funktionsnamen in einer Zeile stehen. Viele Programmierer meinen, der Rückgabewert sollte in einer eigenen Zeile über der Funktionsdeklaration stehen, denn somit sei der Funktionsname besser zu erkennen5 . Andernfalls kann dem eigentlichen Funktionsnamen eine längere Angabe der Sichtbarkeitsstufe (public, protected oder private) sowie ein lang werdender Ergebnistyp vorausgehen. So finden wir etwa einige lange Zeilen in der Funktionsimplementierung (von Sun):
public void addLayoutComponent(Component comp, Object constraints)
private static void testColorValueRange(float r, float g, float b)
public static AdjustmentListener remove(AdjustmentListener l,
AdjustmentListener oldl)
Bei der dritten Zeile sehen wir das Dilemma. Nach dem Kodierungsstil von GNU schwächt sich die Länge der Zeile etwas ab:
public static AdjustmentListener
remove(AdjustmentListener l, AdjustmentListener oldl)
Beide Formulierungen haben ihre Vor- und Nachteile. In der Sun-Variante erblicken wir nach den Schlüsselwörtern public und void, die ja heutzutage immer farblich hervorgehoben sind, den Namen des Rückgabetyps und dann den Namen der Funktion, etwa im ersten Beispiel addLayoutComponent. Unser Auge muss also zuerst über public/private/protected und den Rückgabetyp springen, der wie bei AdjustmentListener lang werden kann, um dann den interessierenden Funktionsnamen aufzunehmen. Bei der unteren Variante jedoch kann das Auge immer am linken Rand die Information finden, die es braucht. Doch leider scheint die Argumentation nicht ganz so einfach zu sein, denn benutzen wir zwei Zeilen, ist der Name der Funktion vom darüberliegenden public void etwas verstellt und alles wirkt gedrungen und dicht. Ich empfehle daher die erste Variante, die etwas luftiger ist und den Funktionsnamen mehr in die Mitte rückt. Also:
public static AdjustmentListener remove( AdjustmentListener l,
AdjustmentListener oldl )
Im Gegensatz zu der Schreibweise von Sun steht jedes Argument in einer Zeile und die Klammern sind mit Leerzeichen etwas freigeräumt.
Nach der Parameterliste sind die geschweiften Klammern des Blocks immer in die nächste Zeile unter den ersten Buchstaben des Funktionsnamens zu schreiben. Beim Durchlaufen des Programmcodes können wir uns besser an den öffnenden Klammern orientieren.
Der Punktoperator für den Zugriff auf Objekt- oder Klassenvariablen bzw. -methoden, darf nicht durch Freiraum von dem Klassenname bzw. dem Ausdruck für die Objektreferenz oder dem Methoden- bzw. Variablennamen abgetrennt werden. So ist es vernünftiger anstatt
persons. whoAmI . myName
persons.whoAmI.myName
zu programmieren. Obwohl der Java-Compiler auch Programmcode mit eingebetteten Kommentaren und Zeilenvorschübe akzeptiert, ist vor Konstruktionen wie
persons./* um wen geht es */whoAmI/* ich bin's*/.
/*mein Name*/myName
Ausdrücke begegnen uns unentwegt in Programmen. Da sie so eine hohe Bedeutung haben, ist auf gute Lesbarkeit und Strukturierung zu achten. Einige Punkte fallen besonders ins Gewicht:
| Shift-Operatoren als Ersatz für arithmetische Operationen sind zu vermeiden. Für einen Compiler ist es gleich, ob er eine Zahl um zwei Stellen nach links shiftet oder mit vier multipliziert – der generierte Code sollte optimiert derselbe sein. |
| Um die Reihenfolge der Operationen in komplexen Ausdrücken zu enthüllen, sind Klammern zu setzen. Bezüglich der Auswertungsreihenfolge gibt es in Java einige Fallen. Die Abfrage, ob zwei Werte kleiner als eine Zahl sind, ist mit dem Ausdruck a&b < 10 falsch gelöst; die Interpretation ist a&(b<10), also ist diese Anweisung doppelt falsch, außer, wenn a ein boolean ist. Ungeschickt eingestreute Leerzeichen können hier ganz schnell falsche Ausführungsreihenfolgen suggerieren, deshalb ist besondere Vorsicht geboten. |
| Glücklicherweise unterstützt die Syntax von Java es nicht, Wahrheitsausdrücke aus Vergleichen mit in einen Ausdruck einzubringen, der nicht den Typ boolean hat. In C ist es kein Problem, beispielsweise durch a=27*(b<10) einer Zahl a genau dann den Wert 27 zuzuweisen, wenn b kleiner 10 ist. (Ist b<10 wahr, dann ist dieser Ausdruck wahr und somit ist a=1*27=27. Ist b aber größer oder gleich 10, dann ergibt der Vergleich b<10 eine 0, und a=0*27=0.) In diesem Fall brauchen wir noch nicht einmal auf eine if-Verzweigung zurückzugreifen, denn mit dem trinären Operator geht es auch: a = (b<10) ? 27 : 0. |
a = (b = c + d) + e;
eine Vereinfachung im Vergleich zu
b = c + d;
a = b + e;
ist, sollten wir mit einer Zuweisung pro Zeile auskommen.
Zahlenliterale, also umbenannte Konstanten, sollten wir in Ausdrücken vermeiden. Da sie die Werte ändern, hätten wir oft nicht die Übersicht, wo der gleiche Wert die gleiche Bedeutung hat. Konstanten werden in Java mit final deklariert. Von Konstanten mit besonderer Bedeutung, so genannten »Magic Numbers«, ist abzuraten. Wenn der Wert einer solchen magischen Variable später geändert wird, zaubert das Programm auf einmal etwas ganz anderes daraus, da sich die Änderung an vielen Programmstellen auswirkt. Wir sollten Konstanten definieren für numerische Werte (z. B. PI, Umrechnungsfaktoren) oder Werte von Aufzählungstypen.
Zu den Einrückungen von Anweisungen, insbesondere den Kontrollstrukturen, wurde schon weiter vorn in diesem Kapitel etwas gesagt. Wir wollen an dieser Stelle auf die einzelnen Elemente eingehen.
Der Einsatz der Schleifen hängt natürlich von ihrem Verwendungszweck ab. for-Schleifen sollten immer dann benutzt werden, wenn eine Variable um eine konstante Größe erhöht wird. Tritt in der Schleife keine Schleifenvariable auf, die inkrementiert oder dekrementiert wird, sollte eine while-Schleife genutzt werden. Eine do/while-Schleife sollte dann ihren Einsatz finden, wenn die Abbruchbedingung erst am Ende eines Schleifendurchlaufes ausgewertet werden kann. Variablen, die eine Größe oder Länge bezeichnen, können beim Durchlauf unrealistischerweise negative Werte annehmen. Leider können in Java keine Typen ohne Vorzeichen deklariert werden – lassen wir char einmal außen vor –, so wie in C für viele Datentypen unsigned erlaubt ist.
Für Bereichsangaben der Form a>=23 && a<=42 ist es empfehlenswert, den unteren Wert mit in den Vergleich einzubeziehen, den Wert für die obere Grenze jedoch nicht (inklusive untere Grenzen und exklusive obere Grenzen). Für unser Beispiel, in dem a im Intervall bleiben soll, ist Folgendes besser: a>=23 && a<43. Die Begründung dafür ist einleuchtend:
| Die Größe des Intervalls ist die Differenz aus den Grenzen |
| Ist das Intervall leer, so sind die Intervallgrenzen gleich |
| Die untere Grenze ist nie größer als die obere Grenze. |
Die Standard-Bibliothek verwendet auch durchgängig diese Konvention bei substring(), subList() oder Array-Indexwerte.
Die Vorschläge können für normale Schleifen mit Vergleichen übernommen werden. So ist eine Schleife mit zehn Durchgängen besser in der Form
for( a = 0; a < 10; a++ )
formuliert, als in der semantisch äquivalenten Form
for( a = 0; a <= 9; a++ )
Wir können unsere Programme lesbarer machen, indem wir auf continue verzichten. Außerdem können wir ein break benutzten, anstatt mit Flags aus einer Schleife vorzeitig auszubrechen. Dazu zwei Beispiele. Folgendes ist zu vermeiden:
boolen endFlag = false;
do
{
if ( Bedingung )
{
// Code ohne Ende
endFlag = true;
}
} while ( weitereBedingung && !endFlag );
do
{
if ( Bedingung )
{
// Code wieder ohne Ende
break;
}
} while ( weitereBedingung );
Die alternative Lösung macht natürlich einen Unterschied, falls nach dem if noch Anweisungen in der Schleife stehen.
Ein continue innerhalb einer if-Abfrage kann durch einen else-Teil bedeutend klarer gefasst werden. Zunächst das schlechte Beispiel:
while ( Bedingung ) // Durch continue verzuckert
{
if ( NochNeBedingung )
{
// Code,Code, code
continue;
}
// Weiterer schöner Code
}
while ( Bedingung )
{
if ( NochNeBedingung )
{
// Code, Code, Code
}
else
{
// Weitere schöner Code
}
}
Es ist eine Eigenschaft der case-Zweige einer switch-Anweisung, dass ein Zweig, der nicht durch ein break abgeschlossen ist, im nächsten case-Zweig weiter ausgeführt wird. Dies ist eine große Fehlerquelle. Wir betrachten wieder ein Beispiel:
switch ( Tag )
{
case A:
{
// Code der durchfällt
}
case B:
{
lieblingsFunktion();
// und noch Code dazu
break; // jetzt erst switch-Anweisung verlassen
}
case C:
{
}
}
Günstiger ist es, die absichtlich durchfallenden case-Zweige kenntlich zu machen, beispielsweise so:
switch ( Tag )
{
case A:
{
// Code der durchfällt
} // FALLTHROUGH FALLTHROUGH
case B:
{
lieblingsFunktion();
// und noch Code dazu
break; // jetzt erst switch-Anweisung verlassen
}
case C:
{
}
}
Eine Klasse kann in verschiedene Sektionen aufgeteilt werden, die beschreiben, ob Informationen frei, frei für die Nachkommen oder gegen alle Zugriffe von außen geschützt sind. Diese Angaben sollten im Quelltext in der Reihenfolge
| public |
| protected |
| paketsichbar |
| private |
angegeben werden. Der öffentliche Teil befindet sich deswegen am Anfang, da wir uns so schnell einen Überblick verschaffen können. Der zweite Teil ist dann nur noch für die erbenden Klassen interessant, und der letzte Teil beschreibt allein geschützte Informationen für die Entwickler. Ein Beispiel einer einfachen Klasse:
public class Ulli
{
public Ulli() // Default constructor
{
...
}
protected int changeResidence( String resi );
{
...
}
private String residence;
private int age;
}
Der Zugriff auf öffentliche (public) Variablen bzw. für die Nachkommen lesbare Variablen (protected) sollen vermieden werden. Damit verhindern wir verschiedene Probleme:
| Eine öffentliche Variable widerspricht der Kapselung von Daten. Von außen kann jeder stets die Variable ändern, und dies kann zu Fehlern führen, die schwer zu finden sind. |
| Die interne Repräsentation der Daten wird versteckt und erlaubt es, den Programmcode später noch einmal problemlos zu ändern. Die Klasse muss ihre Arbeitsweise verstecken und nicht den Benutzer der Klasse dazu zwingen, sein Programm immer der Klasse anzupassen. |
| Auch protected-Variablen haben ihre Schwächen, da die abgeleitete Klasse auf diese Attribute direkt zugreifen kann. Die Basisklasse ist dann für immer an die Variablen gebunden und kann sie niemals in ihrer Bedeutung verändern oder gar auf sie verzichten, da die Variable ja von irgendeiner Unterklasse benutzt werden könnte. |
Diese Probleme können wir leicht umgehen, wenn wir zwei Dinge berücksichtigen:
| Auf Variablen wird nur lesend zugegriffen. Da dies leider oft nicht streng genug durchgehalten wird, ist es empfehlenswert, für |
| alle Änderungen an Variablen (auch wenn nur die erbende Klasse Elemente der Basisklasse verändert) die Funktionen getAttribut() und setAttribut() einzuführen. |
Zugriffsmethoden erlauben uns, an die Attribute eines Objekts heranzukommen. Das Motiv für ihren Einsatz ist oben schon genannt worden. Die Funktionen getAttribut() und setAttribut() sind solche Accessoren. Die Vorsilben get-Methode und set-Methode sind auch als eine Art Präfix anzusehen, wie er in der ungarischen Notation vorgesehen ist. Ebenso ist es zweckmäßig, is als Vorsilbe anzusehen, die eine Abfrage macht; isChar() ist ein Beispiel für einen Accessor mit boolean-Ergebnis. Leider haben Accessoren den Nachteil, dass sie den Code aufblähen. Doch angesichts der erhöhten Flexibilität ist dies in Kauf zu nehmen. Wenn sich später einmal die Implementation ändert, müssen die Programmzeilen nicht verändert werden. Dafür kann sich dann hinter der vermeintlich trivialen Funktion etwas ganz anderes verbergen.
Wenn wir Zugriffsfunktionen einführen, dann müssen wir natürlich auf die beabsichtigten Rechte für die darunter liegenden Attribute achten. Sollen diese nach außen sichtbar sein, so sind auch die Zugriffsfunktionen public. Sind die Werte nur für die Erben wichtig, so sollen die Funktionen protected deklariert werden. Nach Einführung der Zugriffsfunktionen werden die zugehörigen Objektvariablen private gesetzt.
Da die Einführung der Methoden oft sehr aufwendig ist, ist im Entwicklungsteam abzusprechen, ob nicht nur setXXX()-Methoden implementiert werden. Lesender Zugriff kann ein Objekt nicht verändern und ist somit weniger gefährlich. Damit geben wir natürlich die Unabhängigkeit von der internen Implementierung der Klasse auf.
Wir müssen versuchen, unseren Programmcode in den Funktionen so weit zu strukturieren, dass er nicht mehr als ein bis zwei Seiten lang ist. Ein längerer Text ist schwer zu durchschauen und bei der Fehlersuche schwerer zu testen. Jedoch ist ein langes switch mit vielen cases weniger schlimm als 8-fach geschachtelte Schleifen und Fallunterscheidungen.
Es gibt von den Machern von Java die Java Code Conventions, die Empfehlungen zur guten Formatierung von Quellcode enthalten. Die aktuelle Version ist vom April 1999 und liegt bei Sun auf dem FTP-Server ftp://ftp.javasoft.com/docs/codeconv in einer HTML-, PS und PDF-Version. Eine deutsche Übersetzung von Matthias Klein findet sich unter www. fh-karlsruhe.de/~klma0023.
1 Außer auf einem n-Prozessor-System. Dann ist Bubble-Sort sehr gut.
2 Konvention, die in diesem Buch verwendet wird.
3 Die von Sun vorgeschlagene Konvention.
4 C-Programmierer bauen gerne Konstrukte wie:
while ( /* Bedingung */) Anweisung1, Anweisung2;
Damit versuchen sie geschweifte Klammern zu sparen! Natürlich sind solche Aktionen zu vermeiden!
| << zurück |
| |||||
| |||||
| |||||
| |||||
| |||||
| |||||
| |||||
Copyright © Galileo Press GmbH 2002
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.