Galileo Computing <openbook>
Galileo Computing - Programming the Net
Galileo Computing - Programming the Net


Java ist auch eine Insel von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 1.4
Buch: Java ist auch eine Insel - Zum Katalog
gp Kapitel 27 Style-Guide
  gp 27.1 Programmierrichtlinien
  gp 27.2 Allgemeine Richtlinien
  gp 27.3 Quellcode kommentieren
    gp 27.3.1 Bemerkungen über JavaDoc
    gp 27.3.2 Gotcha-Schlüsselwörter
  gp 27.4 Bezeichnernamen
    gp 27.4.1 Ungarische Notation
    gp 27.4.2 Vorschlag für die Namensgebung
  gp 27.5 Formatierung
    gp 27.5.1 Einrücken von Programmcode – die Vergangenheit
    gp 27.5.2 Verbundene Ausdrücke
    gp 27.5.3 Kontrollierter Datenfluss
    gp 27.5.4 Funktionen
  gp 27.6 Ausdrücke
  gp 27.7 Anweisungen
    gp 27.7.1 Schleifen
    gp 27.7.2 Switch, Case und Durchfallen
  gp 27.8 Klassen
  gp 27.9 Zugriffsrechte
    gp 27.9.1 Accessors/Zugriffsmethoden
  gp 27.10 Verweise

Kapitel 27 Style-Guide

Kunst ist eine Lüge,
die uns die Wahrheit begreifen lässt.
– Pablo Picasso


Galileo Computing

27.1 Programmierrichtlinien  downtop

Es ist immer ein Problem, für einen selbst, aber noch mehr in der Gruppe, Programme konsistent zu schreiben. Eine Möglichkeit sind Richtlinien, die Hinweise geben, wie Programme korrekt zu schreiben und leicht zu warten sind. Um dieses zu erreichen, sollte der Quellcode verschiedene Kriterien erfüllen. Er sollte

gp  eine konsistente Formatierung haben
gp  leicht zu Warten sein
gp  einfach zu lesen und zu verstehen sein
gp  frei von typischen Fehlern sein

Seit längerer Zeit gibt es Programmierrichtlinien, die genormte Quelltexte hervorbringen sollen, sodass diese auch von anderen Entwicklern nachvollzogen werden können. Unter Programmierrichtlinen fällt nun Verschiedenes: Namensgebung von Variablen, Methoden und Klassen, Einrückung des Programmcodes, Dokumentation und mehr. Wir wollen uns nun mit einigen Punkten näher beschäftigen.

Die Programme »lint« und »indent«, aber auch »CodeCheck« werden von Vielen genutzt, um Java-Programme systematisch auf Portabilitätsaspekte, softwaremetrische Aspekte oder Wartungsfähigkeit zu prüfen. Ein spezielles Buch mit dem Titel »Advanced Java: Idioms, Pitfalls, Styles and Programming Tips« von Chris Laffra, erschienen bei Prentice Hall, beschäftigt sich ebenfalls mit dieser Thematik.


Galileo Computing

27.2 Allgemeine Richtlinien  downtop

Einige Untersuchungen an großen Softwarepaketen zeigen, dass mitunter Programmstücke hoch optimiert, aber dann doch nicht ausgeführt werden. Zumeist ist diese Art der Optimierung gefährlich. Geht sie zugunsten der Übersichtlichkeit, sollte gründlich abgewogen werden, ob wir nur unser Können zeigen wollen oder ob die Optimierung tatsächlich etwas bringt. Es hat keinen Zweck einen Bubble-Sort-Algorithmus in Assembler zu programmieren , denn der Algorithmus ist langsam und nicht seine Implementierung in einer Hochsprache. Wir sollten nicht vergessen, dass Compiler heutzutage so gut optimieren, dass wir uns ruhig erlauben können, klar anstatt unübersichtlich zu programmieren.


Galileo Computing

27.3 Quellcode kommentieren  downtop

Jede Datei, die Quelltext in irgendeiner Form enthält, muss dokumentiert werden. Aus einigen einleitenden Zeilen muss deutlich werden, was der Quelltext macht. Daneben sollten auch Copyright-Informationen Platz finden. Kommentare in den öffentlichen Klassen müssen so verfasst sein, dass diejenigen, die die Klassen auch benutzen, etwas damit anfangen können. Somit hat die Dokumentation der Funktionalität eine ganz andere Qualität, als die Dokumentation des Quelltexts, die in erster Linie für die Programmierer interessant ist.

Alle Kommentare und Bemerkungen sollten in Englisch verfasst werden, um Projektmitgliedern aus fremden Ländern das Lesen zu vereinfachen. Für Kommentare sollten wir die Zeichen // benutzen. Damit kann besser überschaut werden, wo Kommentare beginnen und wo sie enden. Einen Quelltext zu überschauen, der für mehrere Seiten mit den Kommentarzeichen /* und */ unterbrochen wird, ist schwierig. Der Einsatz dieser Zeichen eignet sich besser dazu, während der Entwicklungs- und Debugphase Codeblöcke auszukommentieren. Benutzen wir zur Programmdokumentation gleich die Zeichen /* und */, so sind wir eingeschränkt, denn Kommentare dieser Form können wir nicht schachteln.

Ein Grundgerüst für einen einleitenden Kopf einer Datei könnte wie folgt aussehen:

/*
* @(#)Test.java
* Description: This is a test program
* Rev: C
* Created: Wed. June 25, 1997, 21:22:23
* Author: Christian Ullenboom
* mailto: C.Ullenboom@java-tutor.com
*
* Copyright Universität Paderborn
* Warburger Str. 100
* 33098 Paderborn – Germany
*
* The copyright to the computer program(s) herein
* is the property of University Paderborn, Germany.
* The program(s) may be used and/or copied only with
* the written permission of Paderborn University
* or in accordance with the terms and conditions
* stipulated in the agreement/contract under which
* the program(s) have been supplied.
*
* CopyrightVersion 1.1_beta
*/

Ebenso wie einleitende Worte jede Datei beschreiben, geht die Dokumentation in der benutzten Klasse und den Funktionen weiter. Nach dem oberen Block, welcher die Datei als Ganzes beschreibt, folgt in der Regel die Definition des Pakets, dann die Import-Klauseln und anschließend die Deklaration der implementierten Klassen. Das folgende Beispiel stammt aus dem java.awt.Button-Modul:

package java.awt;

import java.awt.peer.ButtonPeer;
import java.awt.event.*;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.IOException;

/**
* A class that produces a labeled button component.
*
* @version 1.37 03/13/97
* @author Sami Shaio
*/
public class Button extends Component {
...
}

Quellcode muss kommentiert werden. Die Kommentare sollten einfach zu finden sein und in natürlicher Sprache verfasst sein. Sind die Bezeichnernamen gut gewählt, so ist auch weniger an Quelltext zu kommentieren. Programmieren wir über eine längere Zeit, so müssen wir auch angeben, wann wir mit der Implementierung begonnen haben und wann wir Änderungen durchführen. Gerade deshalb ist es wichtig, mit JavaDoc zu arbeiten, denn die Dynamik eines Programms wird festgehalten und die externe Dokumentation ist immer aktuell. Kommentare können strategisch oder taktisch sein.

Hinweis Strategische und taktische Kommentare: Ein strategischer Kommentar beschreibt, was eine Funktion macht und wird deshalb vor der Funktion platziert. Die strategischen Kommentare sind auch im von JavaDoc verwendeten DocStyle zu verfassen. Ein taktischer Kommentar beschreibt Programmzeilen und wird, wenn möglich, ans Ende der zu erklärenden Zeile gestellt.

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

/**
* Returns a BigInteger whose value is (this + val).
*/
public BigInteger add(BigInteger val) throws ArithmeticException {
if (val.signum == 0)
return this;
else if (this.signum == 0)
return val;
else if (val.signum == signum)
return new BigInteger(plumbAdd(magnitude, val.magnitude),
signum);
else if (this.signum < 0)
return plumbSubtract(val.magnitude, magnitude);
else /* val.signum < 0 */
return plumbSubtract(magnitude, val.magnitude);
}

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.

Beispiel Ein taktischer Kommentar
else                      // val.signum 
< 0

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.


Galileo Computing

27.3.1 Bemerkungen über JavaDoc  downtop

JavaDoc erstellt aus den strategischen Kommentarzeilen

/**
* Returns a BigInteger whose value is (this + val).
*/
public BigInteger add(BigInteger val) throws ArithmeticException {

eine HTML-Datei, die Anwendern der Klasse einen Überblick über den Prototyp der Funktion gibt. Wir sehen sofort: Die Funktion ist public, sie trägt den Namen add, erlaubt einen Übergabeparameter vom Objekttyp BigInteger und gibt auch, wenn sie nicht gerade eine ArithmeticException auslöst, ein BigInteger zurück.

Es ist sehr nützlich, in die Kommentare HTML-Code einzubetten. Die gewöhnlichen HTML-Kommandos sind dabei zugelassen – näheres dazu findet sich im vorhergehenden Kapitel über JavaDoc. Ein anderes Beispiel macht den Einsatz von DocComments mit HTML-Tags noch deutlicher und soll noch einmal eine gut kommentierte Klasse zeigen. Es handelt sich diesmal um die Klasse InetAddress:

/**
* This class represents an Internet Protocol (IP) address.
* <p>
* Applications should use the methods <code>getLocalHost</code>,
* <code>getByName</code>, or <code>getAllByName</code> to
* create a new <code>InetAddress</code> instance.
*
* @author Chris Warth
* @version 1.42, 02/23/97
* @see java.net.InetAddress#getAllByName(java.lang.String)
* @see java.net.InetAddress#getByName(java.lang.String)
* @see java.net.InetAddress#getLocalHost()
* @since JDK1.0
*/

public final
class InetAddress implements java.io.Serializable {
...
}

Galileo Computing

27.3.2 Gotcha-Schlüsselwörter  downtop

Besondere Eigenschaften eines Quelltexts müssen hervorgehoben werden. Dazu dienen Kommentare, die aber in verschiedenster Form vorkommen. Es ist dabei nur von Vorteil, wenn die Kommentare in Kategorien eingeteilt werden: Diese Programmstelle ist besonders trickreich, die andere beschreibt eine mögliche Fehlerquelle und so weiter. Wir wollen spezielle Kommentarmarkierungen (Gotcha Schlüsselwörter) verwenden, die später von Programmen weiterverarbeitet werden. So könnte ein kleines Shell-Skript schnell spezielle Schlüsselwörter raussuchen und in einer Liste zusammenfassen. Diese Liste kann dann zur Nachbearbeitung nützlich sein.

Tabelle 27.1   Gotcha-Schlüsselwörter und ihre Bedeutung
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

Galileo Computing

27.4 Bezeichnernamen  downtop

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.


Galileo Computing

27.4.1 Ungarische Notation  downtop

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.


Galileo Computing

27.4.2 Vorschlag für die Namensgebung  downtop

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:

gp  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.
gp  Von Sonderzeichen sollte Abstand genommen werden, auch wenn Java diese Zeichen zulässt, da sie im Unicode-Zeichensatz definiert sind.
gp  Setzt sich ein Bezeichnername aus mehreren Wörtern zusammen, so werden diese zusammengeschrieben und die jeweiligen Anfangsbuchstaben groß gesetzt. (KaugummiGenerator, anzahlKaugummis).
gp  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.
gp  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.
gp  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.
gp  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.
gp  Namen dürfen keine Abkürzungen enthalten, die nicht allgemein anerkannt sind. So ist der Bezeichner groupID sicherlich besser als grpID.
gp  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«?
gp  Bezeichner von Variablen und Methoden beginnen mit kleinen Buchstaben (int myAbc; float downTheRiverside; boolean haveDreiFragezeichen).
gp  Bezeichner von Klassen beginnen mit großen Anfangsbuchstaben (class OneTwoTree)
gp  Bezeichner müssen »sprechend«, also selbsterklärend, gewählt werden.
gp  Ausnahme bilden Schleifenzähler. Diese werden oft einbuchstabig gewählt, wie i, k.
gp  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.
gp  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.


Galileo Computing

27.5 Formatierung  downtop

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.


Galileo Computing

27.5.1 Einrücken von Programmcode – die Vergangenheit  downtop

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):

gp  Der Programmierstil von Kernighan, Ritchie (1978) wird in einem weitreichenden Buch über C-Programmierung festgelegt. Heutzutage sehen die Programme antiquiert aus.
gp  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.
gp  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.
gp  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++.


Galileo Computing

27.5.2 Verbundene Ausdrücke  downtop

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 )                     if ( Bed ) {  
{ ...            {                              ...
}                ...                         }
                 }

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.


Galileo Computing

27.5.3 Kontrollierter Datenfluss   downtop

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ändnissen . 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
{
}

Galileo Computing

27.5.4 Funktionen  downtop

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 erkennen . 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

die Zeile

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

dringend abzuraten.


Galileo Computing

27.6 Ausdrücke  downtop

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:

gp  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.
gp  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.
gp  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.

Auch wenn wir meinen, dass

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.


Galileo Computing

27.7 Anweisungen  downtop

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.


Galileo Computing

27.7.1 Schleifen  downtop

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:

gp  Die Größe des Intervalls ist die Differenz aus den Grenzen
gp  Ist das Intervall leer, so sind die Intervallgrenzen gleich
gp  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++ )

Das Schlüsselwort continue

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 );

statt dessen schreiben wir:

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
}

Viel deutlicher ist:

while ( Bedingung )
{
if ( NochNeBedingung )
{
// Code, Code, Code
}
else
{
// Weitere schöner Code
}
}

Galileo Computing

27.7.2 Switch, Case und Durchfallen  downtop

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:
{
}
}

Galileo Computing

27.8 Klassen  downtop

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

gp  public
gp  protected
gp  paketsichbar
gp  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;
}

Galileo Computing

27.9 Zugriffsrechte  downtop

Der Zugriff auf öffentliche (public) Variablen bzw. für die Nachkommen lesbare Variablen (protected) sollen vermieden werden. Damit verhindern wir verschiedene Probleme:

gp  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.
gp  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.
gp  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:

gp  Auf Variablen wird nur lesend zugegriffen. Da dies leider oft nicht streng genug durchgehalten wird, ist es empfehlenswert, für
gp  alle Änderungen an Variablen (auch wenn nur die erbende Klasse Elemente der Basisklasse verändert) die Funktionen getAttribut() und setAttribut() einzuführen.

Galileo Computing

27.9.1 Accessors/Zugriffsmethoden  downtop

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.

Allgemeines

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.


Galileo Computing

27.10 Verweise  toptop

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!

5    So argumentieren jedenfalls viele Programmierer von GNU.

  

Java 2




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.


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, fon: 0228.42150.0, fax 0228.42150.77, info@galileo-press.de