StyleGuidelines für Java und .NET

Seminararbeit von Klaus Lehner
Spezielle Kapitel aus Softwareentwicklung: Programmierstil
WS 2002 / 03, JKU Linz

Inhaltsverzeichnis

1. Wozu Guidelines?

2. Allgemeine Formatierung

3. Namenskonventionen

4. Deklarationen

5. Statements

6. Verzweigungen & Schleifen

7. Whitespaces

8. Dokumentation

9. CheckStyle

10. Anwendung in der Praxis

11. Zusammenfassung

12. Referenzen


1. Wozu Guidelines?

Java und C# bieten alle Freiheiten, für andere völlig unlesbaren Code zu schreiben. Durch einige Regeln und Richtlinien kann man sich und vor allem allen anderen jedoch das Leben um einiges erleichtern.

Sogenannte "Cowboy-Programmierer" sind fest davon überzeugt, dass es nicht nötig ist, seinen Programmierstil an gewisse Techniken anzupassen, da dies angeblich nur unnötig Zeit koste, was jedoch, wenn man sich folgende Graphik vor Augen hält, ein absolut falscher Schluss ist:


Aus diesem Diagramm ist einfach zu erkennen, dass die Wartung von Source Code in den letzten Jahren immer größere Bedeutung bei den laufenden Kosten eines Projekts annimmt. Sun beziffert den Anteil der Wartung in den Softwarekosten sogar mit bis zu 80 Prozent.

Und genau dies ist auch in der Praxis zu beobachten: Fast bei jedem Projekt müssen später andere Leute den Source Code warten, als die, die ihn geschrieben haben. Viele Programmierer, die nun mit fremdem Source Code weiterarbeiten müssen, sind oft über anderen Programmierstil dermaßen verärgert, dass sie nicht davor zurückschrecken, den ganzen Code nocheinmal zu schreiben, und zwar mit ihrem Stil. Dieses Spiel kann nun natürlich ins Unendliche fortgesetzt werden, es entsteht immer mehr redundanter Code, kleine Fehler schleichen sich ein und die Kosten steigen ins Grenzenlose.

Generell ist aber zu sagen, dass es keine verbindlichen Richtlinien gibt, wie gut strukturierter Source Code auszusehen hat. Es gibt verschiedenste Vorgaben (auch z.B. von Sun), wenn man sich jedoch für einen Stil entschieden hat, sollte man diesen auch zusammen mit allen anderen Mitarbeitern durchziehen.

Wie so oft gibt es jedoch auch hier eine goldene Grundregel, an die es sich unbedingt zu halten gilt:

Falls man sich an eine Regel nicht hält, soll man dies dokumentieren.


2. Allgemeine Formatierung

Viele IDE's wie z.B. JBuilder oder MS Visual Studio bieten viele Features zur besseren Lesbarkeit von Source Code an (color coding, highlighting matching braces,...).
Grundsätzlich sollte man jedoch SouceCode so entwickeln, dass er auch in einem DOS-Editor noch gut lesbar ist, also unabhängig von der Entwicklungsumgebung.

Viele Entwickler arbeiten nämlich immer noch auf alten IDE's oder einfachen Editoren, die Formatierungen falsch oder gar nicht interpretieren und dadurch Source Code plötzlich ganz anders aussehen lassen.

Deshalb sollte man sich grundsätzlich an folgende Regeln halten:

  • Für Einrückungen keine Tabulatoren, sondern Leerzeichen verwenden
  • Keine Verwendung von PageBreaks etc.
  • Zeilenlänge auf 80 Zeichen beschränken

Zeilenumbrüche

Sollten Ausdrücke länger als 80 Zeichen werden, so ist die Zeile umzubrechen. Zeilen, die so lang sind, dass ein horizontales Scrollen notwendig wird, erschweren die Lesbarkeit ungemein. Des weiteren bei einem eventuellen Ausdruck der Zeilenumbruch willkürlich durchgeführt, was zu unübersichtlichen Ausdrucken führen kann.
Es ist nicht einfach, allgemein geltende Regeln festzulegen, wie bzw. wo ein solcher Zeilenumbruch stattzufinden hat, grundsätzlich sollte man sich jedoch an folgende Konventionen halten:

  • Zeilenumbruch nach einem Komma
  • Zeilenumbruch nach einem Operator
  • Die neue Zeile an den Anfang des Ausdrucks ausrichten

Beispiele:

totalSum = a + b + c +
           d + e);
		   
function (param1, param2,
          param3);
		  
setText ("Long line split " +
         "into two parts.");
		 
for (tableNo = 0; tableNo < maxTable;  
     tableNo += tableStep)

3. Namenskonventionen

Im Laufe der Zeit haben sich gewisse Namenskonventionen für Felder und Methoden eingebürgert, an die man sich halten sollte, um es anderen Programmieren zu erleichtern, deren Inhalte einfach und schnell zu verstehen.

3.a. Syntaktische Namenskonventionen

Großteils sind diese Konventionen sogar von der jeweiligen Programmiersprache streng vorgegebene Richtlinien, an die es sich in jedem Fall zu halten gilt, um schon auf den ersten Blick verschiedene Arten von Deklarationen unterscheiden zu können.

Grundsätzlich unterscheidet man zwischen 3 verschiedenen Cases:

  • Pascal case: der erste Buchstabe, sowie jeder weitere Anfangsbuchstabe in einem zusammengesetzten Wort Anfangsbuchstabe wird groß geschrieben.
    Beispiel: BackColor
  • Camel case: der erste Buchstabe wird klein geschrieben, jeder weitere Anfangsbuchstabe in einem zusammengesetzten Wort wird groß geschrieben.
    Beispiel: backColor
  • Uppercase: alle Buchstaben werden groß geschrieben
    Beispiel: MAXLEN
  Java .NET
Packages / Namespaces
Camel
Pascal
Klassen
Pascal
Interfaces
Pascal
Pascal; Präfix: I
Methoden
Camel
Pascal
Felder, Parameter
Camel
Property
---
Pascal
Konstanten
Uppercase
Pascal

Desweiteren sollte man keine Namen verwenden, die sich lediglich durch die Groß/Kleinschreibung voneinander unterscheiden.

3.b.) Semantische Namenskonventionen

Sprechende Namen sollen es ermöglichen, den Inhalt von Feldern und Methoden zu verstehen. Abgesehen von den syntaktischen Konventionen (s.o.) gelten hier in Java und .NET die selben Regeln.
  • Englisch verwenden! Vor allem durch das Internet und die OpenSource-Schiene kommt es immer öfter vor, dass Entwickler aus verschiedenen Ländern an ein und dem selben Programm arbeiten. Hierbei hat sich Englisch als Standardsprache etabliert. Außerdem sind auch die Klassenbibliotheken von Java und .NET in Englisch, jegliche Namen in anderen Sprachen würden dadurch schon zweisprachigen Source Code erzeugen.
  • Keine Abkürzungen! Manche Programmierer glauben, dass das Auslassen von Vokalen in Variablennamen den Source Code kompakter machen. Das Gegenteil ist der Fall. Es können sehr schnell Doppeldeutigkeiten entstehen, die wiederum zu Missverständnissen und daraus resultierenden Stilschwächen im Source Code führen können.
  • Keine verneinten Namen bei boolschen Variablen! Werden solche Variablen nämlich wiederum mit einem Negations-Operator versehen, entstehen doppelte Verneinungen, die eine unnötige Komplexität in Programmteile bringen.
    boolean isError;    // NOT:   isNotError 
    boolean isFound;    // NOT:   isNotFound 
    :
    if (!isNotError) ... 
  • Fixe Namen: Gewisse Arten von Variablen sollten immer denselben Namen bzw. den gleichen Prä- oder Postfix haben. Dadurch kann sehr schnell auf deren Inhalt geschlossen werden.

    Iteratoren i, j, k, ...
    Repräsentation einer Anzahl nPoints, nLines; auch: numberOfPoints
    Initialisierungsmethoden initializeFontSet();
    Suchmethoden findNext();
    Berechnungsmethoden computeAverage();

    Einen mehrmaligen Aufruf von computeAverage() wird man sich so vielleicht zweimal überlegen, da der Präfix 'compute' eine aufwändige Berechnung andeutet. Deswegen wird man das Ergebnis dieser Methode aus Laufzeitgründen eher zwischenspeichern, als wenn die Methode z.B. getAverage() heißen würde.

JavaBeans-Namenskonventionen:

Diese Namenskonventionen für Methoden sind eine Mischung aus den vorigen beiden. Sie erlauben zum einen einen sicheren Zugriff auf private-Felder, die Namen sind syntaktisch klar erkennbar. Zum anderen werden diese Methoden in der Komponententechnologie für die Realisierung von Properties eingesetzt.

  • set-Methoden: Diese Methoden zum Setzen von Feldern haben den Präfix 'set' und danach den Namen des Feldes.
    public setName(String n) {
      this.name = n;
    }
  • get-Methoden: Diese werden verwendet, um auf den Inhalt von Feldern zugreifen zu können.
    public String getName() {
      return this.name;
    }
  • is-Methoden: Diese werden anstelle der get-Methoden für bool'sche Variablen verwendet, da der Präfix 'is' den semantischen Inhalt der Methode besser repräsentiert.
    public boolean isConnected() {
      return this.connected;
    }

4. Deklarationen

Klassen und vor allem Methoden sollten schon auf den ersten Blick als geschlossene, zusammengehörende Einheiten erkennbar sein. Deshalb ist zwischen Methoden auf jeden Fall eine Zeile freizulassen (gilt nicht für Interfaces!).

Bei Variablendeklarationen sollte man folgende Regeln beachten:

  • Nur gleichartige Variablen in einer Zeile deklarieren. Jede Variable sollte grundsätzlich in einer eigenen Zeile deklariert werden, es sei denn, es handelt sich um zwei oder mehr 'ähnlichen' Variablen, wie z.B. Iteratoren.
    int i,j,k;
  • Deklarationen immer am Anfang eines Blocks
  • Keine äußerliegenden Variablen überdecken
    class Foo {
      int var;
      
      void FooMeth() {
        int var;
    	:
      } // FooMeth
    
    } // Foo

5. Statements

Grundsätzlich sollte man immer nur eine Anweisung pro Zeile schreiben. Source Code wird nicht nur unübersichtlicher, sondern es kann auch beim Debuggen zu Schwierigkeiten kommen, wenn der Debugger nur Zeile für Zeile vorgeht.

Die Lesbarkeit wird auch ungemein erhöht, wenn man zusammengehörende Statements unmittelbar hintereinander schreibt, und evtl. zusammengehörende Blocks durch Leerzeichen voneinander trennt.

schlechtbesser
myObject.message1();
myObject.message2();
counter++;
myObject.message3();
myObject.message1();
myObject.message2();
myObject.message3();

counter++;

6. Verzweigungen & Schleifen

Grundsätzlich sollte man die Anweisungsteile von Verzweigungen und Schleifen immer mit geschwungenen Klammern umschließen, auch wenn es sich um einzeilige Anweisungsblöcke handelt. Oft wird später auf diese Klammern vergessen, wenn eine zweite Zeile hinzugefügt wird.

Die Frage, wo die öffnende Klammer eines Blocks hingehört, ist wohl die am meisten diskutierte, wenn es um Codierungsstandards geht. Programmierer, die noch von der C-Schiene kommen, bevorzugen eine eigene Zeile, in Java hat sich aber folgender Aufbau von Verzweigungen und Schleifen durchgesetzt. Dies ist auch die Empfehlung von Sun.

if (condition) { 
  statements; 
} 

if (condition) { 
  statements; 
} else { 
  statements; 
} 

if (condition) { 
  statements; 
} else if (condition) { 
  statements; 
} else { 
  statements; 
} 

Bei switch-Blöcken, bei denen das break-Statement mit Absicht fehlt (fall through), ist unbedingt ein dementsprechender Kommentar hinzuzufügen. Programmierer, die später diesen Code sehen, könnten ansonsten der Meinung sein, dass auf das break vergessen wurde.

Außerdem sollte auch jedes switch-Statement einen default-Block besitzen, in dem evtl. ein Fehler geworfen werden kann, falls normalerweise die definierten case-Blöcke alle Fälle abdecken sollten.

switch (value) {
  case 1:
    statements;
    /* fall through */
  
  case 2:
    statements;
    break;

  case 3:
    statements;
    break;
 
  default:
    statement;
    break;
}

Schleifen (for, while und do-while) sind genauso aufgebaut wie if-Blöcke.

for (initialization; condition; update) {
  statements;
}
while (condition) { 
  statements; 
} 
do {
  statements;
} while (condition); 

Bei try-catch-Blöcken ist darauf zu achten, dass der catch-Zweig nicht leer ist. Sollte ein leerer catch-Block beachsichtigt sein, so ist dies auf jeden Fall wieder zu dokumentieren.

try {
  statements;
} catch (Exception e) {
  statements;
}

try {
  statements;
} catch (Exception e) {
  statements
} finally {
  statements;
}

7. Whitespaces

Um die Lesbarkeit zu erhöhen, soll auch bewusst mit Leerzeichen umgegangen werden. Zu viele Leerzeichen blähen den Code auf, zu wenige machen ihn genauso unübersichtlich.

Sun empfiehlt folgende Faustregeln:

  • vor und nach Operatoren
  • nach Kommas

Aus folgendem Codestück...

counter=1;
grandTotal=invoice.total()+getAmountDue();
grandTotal=Discounter.discount(grandTotal,this);

würde dadurch dieses entstehen:

counter = 1;
grandTotal = invoice.total() + getAmountDue();
grandTotal = Discounter.discount(grandTotal, this);

Hier gilt wieder genauso wie auch bei der Klammernsetzung, dass ein einheitliches Erscheinungsbild das Wichtigste ist. Hat man sich einmal auf einen Standard geeinigt, sollte man diesen durch den ganzen Code ziehen.


8. Dokumentation

Einer der wichtigsten Bestandteile eines guten Source Codes ist eine kurze, aber einfache und prägnante Dokumentation. Java bietet zur Source Code-Dokumentation JavaDoc an, in .NET werden nach ganz ähnlichem Prinzip XML-Dokumente erzeugt.

Die Funktionsweisen dieser beiden Tools würden den Rahmen dieser Seminararbeit sprengen, deshalb hier lediglich die goldene Regel der Dokumentation.

Source Code, der es nicht wert ist, dokumentiert zu werden, ist es auch nicht wert, geschrieben zu werden.

Dies gilt natürlich nicht für jede geschriebene Zeile, sondern für ganze Klassen, Methoden oder Blöcke. Triviale Kommentare sind nicht notwendig und blähen den Source Code nur auf.


9. CheckStyle

Bei langen Codestücken kann es immer wieder vorkommen, dass auf gewisse Richtlinien vergessen wird.

Viele IDEs bieten umfangreiche Unterstützungen, wie z.B. automatische Einrückungen oder automatische Generierung von Javadoc-Kommentaren. In vielen Fällen ist jedoch ein sogenannter Beautifier nötig, der die Einhaltung der vorgegebenen Richtlininien überprüft.

Checkstyle ist ein vollkonfigurierbares OpenSource-Tool, das ähnlich wie ein Compiler den Source Code durchläuft, ihn aber nicht nach Syntax- oder Semantikfehlern, sondern nach Verletzungen der Richtlinien durchsucht. Es ist sowohl eine Commandline-Version, als auch ein Plugin für den JBuilder verfügbar. Die Konfiguration der einzuhaltenden Richtlinien erfolgt über ein Property-File, bzw. über einen eigenen Menüpunkt im JBuilder.

Checkstyle zeigt die Verletzungen lediglich auf, korrigiert diese aber - wie es andere Beautifier tun - nicht.


10. Anwendung in der Praxis

In der VAI Linz wird seit über einem Jahr ein neues System zur Stranggusskontrolle entwickelt, das das alte System, das aus über 2 Millionen LOC besteht, ersetzen soll. Dieses Projekt wird nach der XP-Philosophie von Kent Beck abgewickelt und dazu gehört auch ein einheitlicher Codierungsstandard.

Dazu wurden zu Projektbeginn alle denkbaren Guidelines in einer Liste zusammengefasst und dann zur Diskussion gestellt. Jeder Entwickler konnte seine Wünsche und Bedenken äußern, der ein oder andere musste zurückstecken, bis schließlich eine Liste von 21 Regeln übrigblieb.

Diese Richtlinien wurden in die für alle zugängliche Projekt-Homepage eingebunden und eingefroren.

Anfängliche Verstöße gegen diese Richtlinien wurden mit drastischen Strafen bedacht (1 Euro in die Kaffee-Kassa für ein "nicht sauberes" Codestück im SourceSafe) und mittlerweile haben sich alle Entwickler an den Standard gewöhnt, was eine allgemeine WinWin-Situation erzeugt.


11. Zusammenfassung

Zusammenfassend kann man noch einmal betonen, dass es den perfekten Codierungsstandard nicht gibt, bei jedem Projekt sollte man sich jedoch auf einen Standard einigen und diesen dann auch konsequent in jeder einzelnen Codezeile anwenden und beachten.


12. Referenzen

Sun Microsystems
http://java.sun.com/docs/codeconv/html/CodeConvTOC.doc.html
Netscape's Software Coding Standards Guide for Java
http://developer.netscape.com/docs/technote/java/codestyle.html
Writing Robust Java Code
http://www.ambysoft.com/javaCodingStandards.pdf
MSDN Library
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconnetframeworkdesignguidelines.asp