'Java Coding Conventions' und
'Best Practices Recommendations'

+ andere TechDocs
+ Sun Code Conventions
+ Collected Java Practices
+


Eine möglichst einheitliche Formatierung und Namensgebung sowie die Verwendung von 'Best Practices' hilft Fehler zu vermeiden, unterstützt die Arbeit im Team und erleichtert die spätere Wartung.



Inhalt

  1. Dateiorganisation und Kommentare
  2. Einrückung, Klammern und Leerzeichen
  3. Naming Conventions, Java-Typen
  4. Best-Practice-Empfehlungen
  5. Vorgehensweisen, Softwarearchitektur, Schnittstellen, Exception Handling, Test und Diagnose


Dateiorganisation und Kommentare

  1. Übliche Reihenfolge der Elemente in einer Java-Sourcedatei:
    1. Datei-Header
    2. 'package'-Anweisung
    3. 'import'-Anweisungen
    4. Javadoc-Kommentar ('/** ... */'), der den Sinn dieser Klasse und Besonderheiten beschreibt
    5. Hauptklasse ('public class')
    6. Klassenvariablen ('static'), geordnet nach Sichtbarkeit, angefangen mit 'public'
    7. Instanzvariablen (ohne 'static'), wieder geordnet nach Sichtbarkeit
    8. 'main()'-Methode (falls vorhanden)
    9. Konstruktoren
    10. Methoden (wichtige zuerst, aber thematisch zusammenhängende Methoden beieinander)
    11. Weitere interne Klassen (falls vorhanden)
    12. Datei-Footer
  2. Ein möglicher Datei-Header:

    /*
     * Project: MeinProjektname
     * $Header: $
     * Author:  MeinName
     * Last Change:
     *    by:   $Author: $
     *    date: $Date:   $
     * Copyright (c): MeineFirma, Jahr
     */
    

    Die Texte zwischen den Dollarzeichen ('$') können durch Versionskontrollsysteme (CVS, Subversion, ...) ersetzt/aktualisiert werden.

  3. Kommentare

    Es ist wichtig zwischen Javadoc-Kommentaren und anderen Kommentaren zu unterscheiden:

    Alle 'public'-Klassen, -Methoden und -Feldvariablen müssen Javadoc-Kommentare ('/** ... */') erhalten (unter Verwendung von '@param', '@return', '@throws', '@see' und '@link'), damit automatisiert eine API-Doku erstellt werden kann. Javadoc-Kommentare müssen alle Informationen beinhalten, die zur Benutzung der Klasse benötigt werden, ohne Einblick in den Sourcecode zu haben. Vermeiden Sie redundante oder duplizierte Kommentare, da diese schnell inaktuell werden. Verwenden Sie stattdessen '@see'.

    Zusätzlich können weitere Kommentare eingefügt werden ('/* ... */' oder '// ...'). Allerdings sollten solche Kommentare kurz und prägnant sein. Anders als Javadoc-Kommentare werden diese Kommentare nur zusammen mit dem Sourcecode gelesen.

    Bitte nur Kommentare mit Inhalt und keine automatisch erstellten Signatur-Wiederholungen.

    Erstellen Sie zusätzlich eine 'package.html'.

  4. Zeilen- und Dateilänge

    Java-Sourcecodezeilen sollten nur ein Statement beinhalten und nicht länger als 120 Zeichen sein.

    Java-Sourcedateien sollten nicht länger als 2000 Zeilen sein.



Einrückung, Klammern und Leerzeichen

  1. Tabulator

    Stellen Sie Ihren Editor so ein, dass er Tabulatorzeichen durch Leerzeichen ersetzt (siehe z.B. Eclipse).

    Die Tabulatorbreite sollte projekteinheitlich auf entweder zwei, drei oder vier Zeichen eingestellt werden.

  2. Leerzeilen

    Innerhalb von Methodendefinitionen wird nicht mehr als eine einzelne Leerzeile verwendet, um Blöcke zu separieren.

    Zwischen Methoden werden eine oder zwei Leerzeilen gesetzt.

    Wenn nach der Hauptklasse noch eine weitere Klasse definiert wird, wird sie mit zwei Leerzeilen abgetrennt.

  3. Geschweifte Klammern

    Die öffnende geschweifte Klammer ('{') kann am Ende der vorherigen Zeile oder in einer eigenen Zeile stehen. Bei Bedingungen und Schleifenkonstrukten ('if', 'for', 'while') wird die öffnende Klammer meistens an das Ende der Bedingungs- oder Schleifenkonstruktionszeile gesetzt, bei Klassen- und Methodendefinitionen meistens in einer eigenen Zeile. Die schließende geschweifte Klammer ('}') steht immer in einer eigenen Zeile, außer es folgt ein zur Bedingung gehörendes Konstrukt wie 'else', 'while', 'catch' oder 'finally'. Der Teil zwischen den Klammern wird eingerückt. Beispiel:

    public class MeineKlasse
    {
      public void meineMethode()
      {
        if( null != getString() ) {
          procede();
        } else {
          System.exit( -1 );
        }
        for( int i=0; i<n; i++ ) {
          doSomething( i );
        }
      }
    }
    

    Bei Bedingungen und Schleifenkonstrukten ('if', 'for', 'while') sollten immer geschweifte Klammern ('{ ... }') gesetzt werden, auch wenn nur eine einzelne Anweisung folgt. Ausnahmen sollte es nur bei wirklich sehr kuzen Ausdrücken geben (z.B. 'if( 10 < i ) i = 0;').

  4. Leerzeichen bei runden Klammern

    Bei Bedingungen und Schleifenkonstrukten ('if', 'for', 'while') sowie bei Methoden gehen die Meinungen auseinander, ob und wo bei den runden Klammern Leerzeichen gesetzt werden sollen.

    Sun schlägt in seinen Code Conventions vor, bei Bedingungen und Schleifenkonstrukten ein Leerzeichen vor die öffnende Klammer zu setzen und bei Methoden keine Leerzeichen zu setzen, zum Beispiel so:

      public void meineMethode(String meinParm1, String meinParm2)
      {
        if (null != getString(meinParm1)) {
          procede(meinParm2);
        }
        for (int i=0; i<n; i++) {
          doSomething(i);
        }
      }
    

    Da so manchmal die Grenze zwischen Methodennamen und Parametern nicht so schnell erfassbar ist, wird oft auch bei Methoden ein Leerzeichen ergänzt:

      public void meineMethode (String meinParm1, String meinParm2)
      {
        if (null != getString (meinParm1)) {
          procede (meinParm2);
        }
        for (int i=0; i<n; i++) {
          doSomething (i);
        }
      }
    

    Da bei Bedingungen, Schleifenkonstrukten und Methoden die Klammern nicht lediglich gruppierende Klammern sind, sondern notwendigerweise zum Programmierkonstrukt gehören, wird die öffnende Klammer auch oft direkt an das Programmierkonstrukt gesetzt und das Leerzeichen dahinter gesetzt:

      public void meineMethode( String meinParm1, String meinParm2 )
      {
        if( null != getString( meinParm1 ) ) {
          procede( meinParm2 );
        }
        for( int i=0; i<n; i++ ) {
          doSomething( i );
        }
      }
    

    Eine ähnliche Formatierung empfiehlt auch die Apache-Maven-Gruppe, siehe http://maven.apache.org/developers/conventions/code.html (dort wird auch eine in Eclipse importierbare Codestyle-Formatierungsvorlagedatei angeboten).



Naming Conventions, Java-Typen

  1. Sprache

    Technische Bezeichnungen (z.B. systemtechnische und Pattern-Namen) sollten englisch sein. Fachliche Bezeichnungen sollten in der Sprache des Fachkonzepts (z.B. Deutsch) sein. Dabei kann es bei zusammengesetzten Wörtern zu Sprachmischungen kommen (z.B. 'getBonitaet()'). Internationalen Kunden zugängliche Interface-Beschreibungen sollten komplett in Englisch sein.

  2. package, import

    'package'-Namen werden komplett in Kleinbuchstaben und ohne Unterstriche geschrieben. Die Namen der eigenen Pakete beginnen mit dem umgekehrten Domainnamen ('de.meinefirma.<projekt>.<kontext>').

    Die 'package'-Struktur und -Hierarchie sollte möglichst der Softwarearchitektur entsprechen (z.B. Aufteilung in Präsentations-, Geschäftslogik- und Persistenzschicht). Falls die Klassen eines 'packages' einem Pattern entsprechen, sollte das 'package' so heißen (z.B. 'dao').

    Interfaces und Superklassen sollten in der 'package'-Hierarchie entweder parallel, gleich oder höher angeordnet sein, aber nicht tiefer als die abgeleiteten Unterklassen.

    Die 'import'-Anweisungen werden alphabetisch sortiert, allerdings werden zuerst die 'java...'-Imports und erst danach andere aufgeführt. Bevorzugen Sie aufgelöste Importanweisungen mit Klassennamen und ohne '.*'. Moderne IDEs unterstützen das Sortieren benötigter und Aussortieren unnötiger 'import'-Anweisungen (Eclipse z.B. mit 'Strg+Shift+O').

    package de.meinefirma.myprojectxy.mysubpackage;
    
    import java.io.BufferedInputStream;
    import de.meinefirma.myprojectxy.businesslogic.MeineEntityKlasse;
    
  3. class, interface

    Klassennamen (und Interface-Namen) beginnen mit einem Großbuchstaben und werden weiter mit Kleinbuchstaben geschrieben, aber bei zusammengesetzten Wörtern beginnt jedes interne Wort wieder mit einem Großbuchstaben ('Kamel'-Schreibweise). Sie enthalten keine Unterstriche.

    Klassennamen sollten möglichst aus Substantiven gebildet werden.

    Falls die Klasse eine bestimmte häufiger vorkommende generische Aufgabe oder ein Pattern umsetzt, kann dies durch ein Postfix im Klassennamen angedeutet werden. Wenn die Hauptaufgabe einer Klasse die Implementierung eines Interfaces ist, heißt die Klasse oft wie das Interface mit einem angehängten 'Impl'. Das Interface hat oft kein Anhängsel, kann aber auch auf 'If' enden. Nach außen sichtbare Interfaces können auch auf 'Dienst' enden.

    Klassennamen sollten innerhalb eines Projekts einmalig sein, also auch in anderen Subpackages nicht vorkommen (dann werden sie nicht verwechselt und können leichter gefunden werden, in Eclipse z.B. mit 'Strg+Shift+T').

    class ImageConverter
    class BusinessEntity
    class ComparatorImpl
    
  4. Methoden

    Methoden werden klein geschriebenen, aber bei zusammengesetzten Wörtern beginnt jedes interne Wort wieder mit einem Großbuchstaben. Sie enthalten keine Unterstriche.

    Methodennamen sollten mit Verben beginnen.

    getColor()
    setAttribute()
    createInstance()
    
  5. Attribute, Variablen

    Die Schreibweise von Variablen ist wie die der Methoden. Sie sollten nicht mit '_' oder '$' beginnen und normalerweise auch nicht mit einem den Typ spezifizierenden Präfix.

    Variablennamen sollten die Bedeutung verdeutlichen, möglichst als Substantiv. Einbuchstabige Variablennamen sollten nur in sehr kurzen temporären Blöcken verwendet werden (z.B. 'for( int i=0; i<n; i++ ) ...'). In solchen Fällen sollten i, j, k, m, n für Integers, c für Characters und s1, s2 für Strings bevorzugt werden.

    Unterscheiden Sie genau zwischen Klassenvariablen ('static'), Instanzvariablen (im Klassenkopf deklariert, ohne 'static') und lokalen innerhalb von Methoden temporär definierten Variablen. Wichtig sind auch die verschiedenen Sichtbarkeiten (z.B. 'private', 'public') und weitere Modifizierer (z.B. 'final').

    Oft ist es besser, Klassen- und Instanzvariablen 'private' zu deklarieren und den externen Zugriff über Getter- und Setter-Methoden zu ermöglichen ('getColor()', 'setAttribute()'). Statt vieler kleiner Setter-Methoden, die einzelne Attribute setzen, kann eine zusammengefasste Methode sinnvoller sein. Immer benötigte Parameter können direkt im Konstruktor angefordert werden.

    Innerhalb von Methoden deklarierte Variablen sollten mit einem Startwert initialisiert werden.

    Vermeiden Sie das Verdecken in der Ableitungshierarchie höher deklarierter Variablennamen.

    public class MeineKlasse
    {
      public  static final int WIDTH_MAX = 999;    // Konstante
      private static int accessCounter;            // Klassenvariable
      private String color;                        // Instanzvariable
    
      public void meineMethode( int myParm )       // Parametervariable
      {
        int size = 0;
        String username = null;                    // Methodenvariable
    
        if( null != getString() ) {
          procede();
        }
        for( int i=0; i<n; i++ ) {                 // lokale Zählvariable
          doSomething( i );
        }
      }
    }
    
  6. Konstanten

    Konstanten werden komplett in Großbuchstaben geschrieben. Interne Wortbestandteile werden durch Unterstriche ('_') voneinander getrennt.

    Konstanten sollten nicht direkt im Code zum Beispiel als Zahl eingefügt werden (keine 'Magic Numbers'), sondern im Klassenkopf als 'static final'-Konstante definiert werden.

    static final int    WIDTH_MIN = 100;
    static final int    WIDTH_MAX = 999;
    static final String MEIN_NAME = "Torsten";
    

    Eventuell macht es Sinn, die Konstanten aus den verstreuten Sourcedateien zu extrahieren und gesammelt über ein spezielles Konstanten-Objekt zugänglich zu machen (welches vielleicht .properties-Dateien, XML-Dateien oder eine Datenbank befragt).



Best-Practice-Empfehlungen

  1. Grundbegriffe

    Verwenden Sie die Java-Grundbegriffe im richtigen Zusammenhang (einige finden Sie erklärt in 'java-basics.htm'). Verwechseln Sie zum Beispiel nicht 'überladen', 'überdecken', 'überschreiben' und 'ableiten' sowie 'final', 'finally' und 'finalize'. Machen Sie sich die unterschiedliche Bedeutung des Modifizierers 'final' bei Variablen, Methoden und Klassen klar. Verstehen Sie die Konzepte von Polymorphie und Interfaces.

  2. Parameterüberprüfung

    Alle 'public'-Methoden müssen ihre Eingangsparameter auf unzulässige Werte überprüfen. Die Methoden dürfen nicht wegen Nullpointer abstürzen.

  3. StringBuffer.append()

    Das Zusammenfügen vieler Strings zu einem Gesamt-String per '+' (Concatenation) führt zu enormen Ressourcen- und Zeitverbrauch. Verwenden Sie stattdessen 'StringBuffer.append()'.

  4. switch

    Setzen Sie bei 'switch'-Statements auch beim letzten 'case'-Element ein 'break' ein, falls später ein weiteres Element dahinter eingefügt wird.

    Falls von einem 'case' ohne 'break' mit dem nächsten fortgefahren werden soll, fügen Sie einen '/* falls through */'-Kommentar ein, der deutlich macht, dass die 'break'-Anweisung gewollt fehlt.

    Fügen Sie immer einen 'default'-Case ein (eventuell Exception-werfend).

      switch( condition ) {
        case ABC:
          statements;
          /* falls through */
        case XYZ:
          statements;
          break;
        default:
          statements;
          break;
      }
    
  5. Reihenfolge bei Vergleichen

    Bei Vergleichen wird oft der konstante Vergleichswert rechts vom Vergleichsoperator geschrieben ('if( x == 7 )'). Es gibt auch die Empfehlung ihn auf die linke Seite zu setzen ('if( 7 == x )'), weil a) nicht aus Versehen eine Zuweisung entsteht ('x = 7' statt 'x == 7') und weil b) so der kürzere Ausdruck vorne steht:

      if( null != getString( myParmAbc, myParmXyz ) ) {
        procede();
      } else if( 27 == (myValueA - myValueB) / 42 ) {
        System.exit( -1 );
      }
    
  6. 'equals()' versus '=='

    '==' vergleicht Objektreferenzen, während 'equals()' Objekte vergleicht:

      String s1 = new String( "Hallo" );
      String s2 = new String( "Hallo" );
      if( s1 == s2 )        ... // false
      if( s1.equals( s2 ) ) ... // true
    
  7. equals(), compareTo() und hashCode()

    Wenn eine Klasse 'equals()' überschreibt, muss sie auch 'hashCode()' überschreiben. Zwei gleiche Objekte müssen den gleichen Hashcode liefern. Ähnliches gilt für 'compareTo()'. Genaueres hierzu finden Sie unter 'http://www.javapractices.com/Topic28.cjp' und 'http://www.javaworld.com/javaworld/jw-06-2004/jw-0614-equals_p.html'.

  8. Collections

    Verwenden Sie Collections immer mit Generics.

    Bevorzugen Sie Collections (mit Generics) gegenüber Arrays, um Kovarianz-bedingte Probleme zu vermeiden.

    Wählen Sie die geeignetste 'Collection'-Klasse.

    Beachten Sie für Multithreading-Anwendungen, dass die "alten" Collections synchronisiert sind, die "neuen" aber nicht. Die "neuen" Collection-Klassen können Sie über die 'Collections.synchronized...()'-Methoden synchronisiert erhalten.

  9. Multithreading-Sicherheit

    Concurrency (Multithreading, Nebenläufigkeit) gehört zu den schwierigsten Themen der Java-Programmierung. Die möglichen Fehler sind häufig schwer zu erkennen und meistens schwer zu debuggen, da sie vielleicht nur sporadisch und nicht so einfach reproduzierbar auftreten. Beachten Sie die Hinweise in 'java-concurrency.htm' und 'jee-ejb2.htm#Vorbemerkung'.

  10. close() in finally

    Stellen Sie bei Ressourcen, die geöffnet und geschlossen werden, wie zum Beispiel Streams und Datenbankverbindungen, sicher, dass die schließende Methode (z.B. 'close()') in einem 'finally'-Block aufgerufen wird, damit auch im Fehlerfall alle Ressourcen korrekt geschlossen werden, zum Beispiel so:

      Connection cn = null;
      Statement  st = null;
      ResultSet  rs = null;
      try {
        Class.forName( sDbDrv );
        cn = DriverManager.getConnection( sDbUrl, sUsr, sPwd );
        st = cn.createStatement();
        rs = st.executeQuery( "select * from " + sTable );
        ...
      } catch( Exception ex ) {
        ...
      } finally {
        try { if( null != rs ) rs.close(); } catch( Exception ex ) { ... }
        try { if( null != st ) st.close(); } catch( Exception ex ) { ... }
        try { if( null != cn ) cn.close(); } catch( Exception ex ) { ... }
      }
    

    Im 'finally'-Block darf kein 'return' enthalten sein.

  11. SQL

    Verwenden Sie möglichst 'PreparedStatement' statt 'Statement', um die Wahrscheinlichkeit für Probleme mit SQL-Injection und Hochkommata ('O'Brien') in einzufügenden Texten zu reduzieren.

    Verstreuen Sie nicht SQL-Statements über viele Java-Sourcedateien. Konzentrieren Sie alle SQL-Statements auf wenige 'DAO'-Klassen. Noch besser: Zusätzlich alle SQL-Statements in eine Java-.property-Datei auslagern.

    Es dürfen mehrere SQL-'Statement' gleichzeitig geöffnet sein, aber innerhalb eines Statements darf zu jedem Zeitpunkt immer nur höchstens ein 'ResultSet' offen sein. Das 'ResultSet' muss nach Gebrauch geschlossen werden ('rs.close()'). Das geöffnete 'Statement' sollte, aber muss nicht unbedingt geschlossen werden, die Entsorgung kann der Java Garbage Collection überlassen werden. Allerdings kann es einen Datenbank-Fehler auf Grund zu vieler geöffneter Cursor geben, wenn viele Statements geöffnet werden, bevor die Garbage Collection die vorherigen schließen konnte. Die 'Connection' muss unbedingt geschlossen werden.

    Verwenden Sie möglichst keine ODBC-Brücken, sondern direkte JDBC-Type-4-Treiber.

    In bestimmten Anwendungen kann der Einsatz von Tools für objektrelationales Mapping (ORM) Vorteile bieten, wie zum Beispiel 'JPA' oder 'Hibernate'.

  12. Konfigurationsparameter

    Speichern Sie Konfigurationsparameter (z.B. Zugangsparameter zur SQL-Datenbank) nicht im Sourcecode. Verwenden Sie .property-Dateien, XML-Dateien oder den JNDI-Kontext.

  13. Collected Java Practices

    Sehen Sie sich die 'Collected Java Practices' an.



Vorgehensweisen, Softwarearchitektur, Schnittstellen, Exception Handling, Test und Diagnose

  1. Vorgehensweisen und Entwicklungs-Tools

    Bei größeren Projekten kann ein definiertes Vorgehensmodell zum Softwareentwicklungsprozess sinnvoll sein.

    Achten Sie darauf, dass bestimmte zentrale Dokumente stets aktuell und allen Projektbeteiligten bekannt sind. Dies könnten zum Beispiel "Pflichtenheft", "Projektplan", "Risikostudie" und "Testbeschreibung" sein.

    Verwenden Sie ein Versionskontrollsystem, zum Beispiel 'Subversion' oder 'CVS' (Integration in Eclipse siehe Subclipse und CVS) (Informationen zu CVS siehe CVS-Buch).

    Verwalten Sie Fehlerreports und Bearbeitungshistorien nicht mit Excel-Sheets, sondern zum Beispiel mit 'Bugzilla'.

    Vermeiden Sie Vorgehensweisen, die zu stark an einen bestimmten Hersteller oder an eine bestimmte IDE binden. Bevorzugen Sie allgemeinere Vorgehensweisen und Tools.

    Compilierung und Deployment müssen nicht nur mit einer bestimmten IDE, sondern auch mit 'Ant' oder 'Maven' möglich sein (z.B. für automatisiertes Testen und um 'Continuous Integration' zu ermöglichen).

    Wenn die Mitarbeiter eines Projekts mit der gleichen Entwicklungsumgebungs-IDE arbeiten, streben Sie gemeinsam verwendete Einstellungen zur automatischen Formatierung an. Für Eclipse finden Sie dazu konkrete Vorschläge unter java-eclipse-einstellungen.htm.

    Aktivieren Sie in Ihrer Entwicklungsumgebungs-IDE möglichst viele Compiler-Warnmeldungen. Für Eclipse finden Sie dazu konkrete Vorschläge unter java-eclipse-einstellungen.htm#CompilerWarnungen.

    Bemühen Sie sich, die Zahl der gemeldeten Warnungen bei Null zu halten, damit neue Warnungen sofort auffallen.

    Reservieren Sie Zeit, um hin und wieder ein Code Review und Refactoring durchführen zu können. Verwenden Sie dafür auch Tools wie zum Beispiel 'FindBugs', 'JDepend', 'EclipseMetrics', 'Eclipse Checkstyle' und 'Borland Together Analyst Audit'. Der Zeitaufwand lohnt sich bei späterer Wartung und für Erweiterungen.

    Erwägen Sie für unstrukturierte freie Diskussionen und Dokumentensammlungen die Installation eines 'Wiki' (möglichst mit Upload-Möglichkeit), an dem sich jeder im Entwicklungsteam beteiligen kann.

  2. Softwarearchitektur

    Software wird selten so implementiert, wie ursprünglich geplant. Ein Großteil des Zeitaufwands entfällt auf die "letzten 5 %" und auf Arbeiten nach "Fertigstellung". Legen Sie deshalb viel Wert auf gute Architektur und eine gute entkoppelte Komponentenstruktur, um Testbarkeit, Wartbarkeit, Änderbarkeit und Erweiterbarkeit sicherzustellen.

    Unterteilen Sie Ihre Software nicht nur in Schichten (z.B. 'Client', 'Presentation Layer', 'Business Logic Layer', 'EIS / Persistence Layer'), sondern darüberhinaus in möglichst gut entkoppelte Komponenten, die optimalerweise sogar getrennt versioniert werden können. Nutzen Sie nach Möglichkeit die üblichen 'Design Patterns'. Sehen Sie sich die 'SOA'-Ideen, das 'Dependency-Injection-Pattern' und 'Spring' an. Nutzen Sie im 'Presentation Layer' das 'MVC'-Konzept (z.B. mit dem 'Spring MVC Web Framework'). Erwägen Sie für den 'Persistence Layer' ORM-Frameworks (z.B. 'JPA' oder 'Hibernate').

    Denken Sie frühzeitig an Mehrsprachigkeit, Sicherheit, Datenschutz, Performance, Skalierbarkeit und Ausfallsicherheit. Beachten Sie die "Sechs Qualitätsmerkmale für Softwareprodukte".

    Definieren Sie alle für ein Projekt wichtigen Begriffe. Erstellen Sie ein Glossar, besonders für die fachlichen Begriffe und deren softwaretechnische Abbildungen.

  3. Schnittstellen, Interfaces

    Sehen Sie stabile Schnittstellen zwischen Modulen und Komponenten vor.

    Sehen Sie sich die 'SOA'-Ideen an.

    Die wichtigsten Java-Interface-Dateien müssen gut dokumentiert sein. Sie sollten zumindest enthalten:
    - Vorspann, der Grundsätzliches oder für alle Methoden geltendes erläutert
    - Ausgefüllte Javadocs
    - Erläuterung der Returnwerte und Exceptions
    - Kann 'null' returniert werden
    - Erklärung von Besonderheiten, eventuell auch kurze Erläuterung von 'fachlichen' Begriffen

    Verwenden Sie möglichst universelle Schnittstellen, zum Beispiel mit 'SOAP Web Services'.

  4. Exception Handling und Logging

    Verwenden Sie viel Sorgfalt auf Ausnahmebehandlungen und aussagekräftige Fehlermeldungen.

    Machen Sie sich die unterschiedlichen Einsatzbereiche und Vorteile klar von:
    - 'unchecked RuntimeException' (werden nicht deklariert, führen bei Transaktionen zum Rollback)
    - 'checked Exception' (müssen deklariert und behandelt werden, eher bei fachlichen als technischen Ausnahmen)
    - 'assert' (ab Java 1.4, Aktivierung mit '-ea', z.B. pragmatischer Parametertest)
    - DBC (Design by Contract) (z.B. mit 'iContract', "ordentlicher" als 'assert', aber aufwändiger)

    Werfen Sie möglichst nicht allgemeine 'Exceptions' oder 'RuntimeExceptions', sondern speziellere, zum Beispiel 'EJBException', 'IllegalArgumentException', 'IllegalStateException', 'MissingResourceException', 'NullPointerException', 'ProviderException' und 'UnsupportedOperationException'.

    Überprüfen Sie das Werfen korrekter 'Exceptions' im 'JUnit'-Test mit der 'fail()'-Methode.

    Vermeiden Sie Logging per 'System.out.println()'. Verwenden Sie einen einheitlichen Logging-Mechanismus und ein System, welches zur Laufzeit das selektive Ein- und Ausschalten bestimmter oder aller Log-Meldungen über eine einfache textbasierte Steuerdatei erlaubt. Verwenden Sie vorzugsweise 'Log4j'.

    Sehen Sie sich die 'Tipps zum Exception Handling und Logging' an.

  5. Test

    Planen Sie genügend Zeit für Komponententests, Integrationstests, Systemtests/Akzeptanztests, Regressionstests, Performanztests, Lasttests und Abnahmetests.

    Verwenden Sie für White-Box-Tests auf Modulebene möglichst 'JUnit'. Erstellen Sie auch frühzeitig Black-Box-Tests, zum Beispiel mit 'Fit', 'Jemmy', 'Selenium' und 'HttpUnit'. Erzeugen Sie für die Tests verschiedene Datenbankzustände mit 'DbUnit'.

    Erste Tests (z.B. mit JUnit) führt der Softwareentwickler auf seinem Rechner durch. Anschließend erfolgen die Tests auf getrennten "sauberen" Systemen, die alle benötigten Sourcen und Ressourcen ausschließlich aus dem Versionskontrollsystem beziehen.

    Automatisieren Sie die Tests für 'Nightly Builds' oder sogar 'Continuous Integration', zum Beispiel mit Hudson oder TeamCity.

    Überprüfen Sie die Testabdeckung ('Code Coverage'), zum Beispiel mit 'Cobertura', 'EMMA', 'jcoverage' oder 'Hansel'.

    Führen Sie Lasttests durch (z.B. mit 'Apache JMeter' und 'HP LoadRunner') und testen Sie auch das Logging, das Verhalten bei Störungen während Transaktionen und Rollbacks sowie den Zugriffsschutz.

  6. Analyse, Profiling, Diagnose und Monitoring

    Untersuchen Sie Ihr Programm mit 'FindBugs' und 'Firebug' auf typische Fehler.

    Sie können leider nicht per "Runtime.freeMemory()" ermitteln, ob bald ein OutOfMemoryError droht, da "freeMemory" auch im regulären Betrieb kurz vor der Garbage-Collection gegen 0 geht. Ein besserer Indikator kann die Ermittlung der Full-Garbage-Collection-Häufigkeit oder die prozentuale Garbage-Collection-Zeit sein (siehe hierzu jmx-gc.htm).

    Überprüfen Sie den JVM-Zustand und Ihren Speicherverbrauch und ermitteln Sie Speicherlecks, zum Beispiel mit: 'MAT (Eclipse Memory Analyzer, ursprünglich von SAP)', 'VisualVM', 'jconsole', 'Monitoring, Management, and Troubleshooting Tools (jps, jstat, jmap, jstack, jinfo)', 'Overview of Monitoring and Management', 'GCViewer', 'Lambda Probe'.

    Analysieren Sie Performanceengpässe und Memory Leaks während der Entwicklung mit Profilern, wie zum Beispiel: 'NetBeans Profiler', 'Eclipse TPTP (Test & Performance Tools Platform Project)', 'YourKit Java Profiler', 'JProfiler', 'JProbe'.

    Analysieren Sie Performanceengpässe und Memory Leaks während des Produktivbetriebs mit Diagnose-Tools, wie zum Beispiel: 'dynaTrace Diagnostics', 'Quest PerformaSure'.

    Installieren Sie ein standardisiertes Monitoring, zum Beispiel mit: 'Hyperic HQ', 'Nagios', 'PNP4Nagios', 'NagiosExchange', 'Zabbix', 'RHQ'.

    Weitere Hinweise finden Sie unter 'Novakovic/Panienski, Javamagazin 10.2007', 'Novakovic/Bogaard, Javamagazin 12.2007', 'Novakovic/Bogaard, Javamagazin 1.2008', 'Javamagazin 8.2008' und 'Tobias Frech'.

  7. Branches

    Aus vielerlei Gründen können Software-Branches sinnvoll sein. Der häufigste Grund: Software wird nach der Auslieferung weiterentwickelt. Es muss aber jederzeit möglich sein, genau zu dem Softwarestand der Auslieferung Bugfixes zu implementieren und diesen reparierten Softwarestand als eigenen isolierten Branch unabhängig vom Haupt-Branch ausliefern zu können.

    Branches erhöhen die Komplexität. Ein Merging verschiedener Branches ist zeitaufwändig und risikoreich. Schauen Sie sich 'Branching Patterns' an, um die für Sie geeignete Branching-Strategie zu finden (z.B. hier und hier).

    Eine übliche CVS-Branching-Strategie ist der "Deferred Branching Style":

    - Die meisten Softwareänderungen (z.B. an Java-Sourcen) sind sowohl für die aktuell entwickelte als auch für zukünftige Softwareversionen vorgesehen und werden in CVS-Head eingecheckt (bzw. dem Hauptbranch in anderen Version-Control-Systemen). Für diesen häufigsten Fall ist kein Merging notwendig.

    - Bei jeder Softwareauslieferung wird ein CVS-Tag gesetzt, so dass jederzeit zu dieser Auslieferung ein Branch gestartet werden kann.

    - Wenn eine Softwareänderung (z.B. Bugfix) nur für eine bestimmte Softwareversion vorgesehen ist, wird sie nicht in CVS-Head, sondern in den entsprechenden Branch eingecheckt.

    - Bugfixes, die nicht nur für CVS-Head, sondern auch für einen bestimmten Branch gelten sollen, werden sowohl in CVS-Head, sondern auch im entsprechenden Branch realisiert.

    - Die Summe aller eingecheckten Sourcen sollte, aber muss nicht unbedingt, konsistent zueinander sein. Was aber konsistent sein muss, ist die Zusammenstellung der Gesamtkonfiguration der veröffentlichten Modulbibliotheken.

    Alternativ zu diesem "Deferred Branching Style" (aufgeschobenes Branching, Branching erst dann, wenn wirklich ein Softwarestand für eine Auslieferung verwendet wird) kann der "Early Branching Style" eingesetzt werden (frühes Branching, Branching bereits zu Beginn einer neuen Softwareversion, neuer Sourcecode wird nicht in den "Head-Branch", sondern in einen bestimmten Versions-Branch im Version-Control-System eingecheckt).
    Ergebnis: strengere Isolierung, formalerer Entwicklungsprozess, höhere Komplexität, geringere Produktivität, geringeres Risiko für ältere Branches, aber höheres Risikio für aktuellsten Branch wegen höherer Merging-Risiken und -Aufwände.



Literatur





Weitere Themen: andere TechDocs | Sun Code Conventions | Collected Java Practices
© 1998-2007 Torsten Horn, Aachen