Coding Guidelines

Aus MimiPedia

Formatierung

Die Formatierung umfaßt die Verwendung von Leerzeichen, Leerzeilen, Zeilenumbrüchen und Einrückungen. Eine einheitliche Formatierung trägt wesentlich dazu bei, daß Code schnell gelesen werden kann. Zudem werden Kollisionen beim Einchecken in das Code-Repository vermieden die dadurch entstehen, daß Entwickler den Code unterschiedlich formatieren. Die Frage welche Formatierung die Richtige ist, läßt sich nicht beantworten. Der eine Entwickler bevorzugt das eine, der nächste Entwickler findet was anderes besser. Die Regeln bemühen sich, einen Kompromiß zwischen Kompaktheit und Lesbarkeit zu finden.

Die Formatierung sollte wird über einen Eclipse-Formatter gesteuert werden, der über den Workspace Mechanic konfiguriert wird. Die Ausführung des Formatters wird – ebenfalls durch den Workspace Mechanic – als "SaveAction" in Eclipse eingestellt, so daß bei der Code jedem Speichern formatiert wird. Darüber hinaus sorgt der Formatter dafür, daß einzelne Statements in Schleifen und anderen Kontroll-Strukturen in geschweifte Klammern gesetzt werden und überflüssige Leerzeichen am Zeilenende entfernt werden.

Was für die eine eine willkommene Erleichterung darstellt, empfinden andere als Gängelung. Die Formatierung sollte zwar nicht ganz ausgehebelt werden, kann aber – wie weiter unten beschrieben – durch zusätzliche Zeilenumbrüche aufgelockert werden. Wie eingangs betont, sollen Codier-Regeln helfen und nicht behindern, in diesem Sinne sollte der Formatter denn auch als automatische Unterstützung wahrgenommen werden.

Leerzeilen

Der Formatter komprimiert aufeinanderfolgende Leerzeilen, so daß immer nur eine Leerzeile stehen bleibt. Einzelne Leerzeilen werden jedoch nicht entfernt. Der Entwickler kann somit steuern, ob er zum Beispiel zwischen zwei Feld-Definitionen einer Klasse eine Leerzeile lassen möchte oder nicht.

An bestimmten Stellen – zum Beispiel zwischen package- und import-Deklaration – erzwingt der Formatter Leerzeilen, die nicht gelöscht werden können.

Zeilenumbrüche

Der Zeilenumbruch wird vom Formatter automatisch ausgeführt. Das sieht bisweilen nicht so übersichtlich aus, wie man sich das wünscht, zum Beispiel bei langen Parameter-Listen oder bei Methoden-Verkettung. Der Entwickler kann dann zur Erhöhung der Lesbarkeit den Umbruch mit Zeilenkommentaren erzwingen:

private Stream<DtoProdukt> produktStream(DtoPersonendaten person) {
    return Optional.of(person).map(DtoPersonendaten::getProduktListe).orElse(Collections.emptyList()).stream();
}

Durch Zeilenend-Kommentare wird jeder Methoden-Aufruf in eine eigene Zeile gestellt:

private Stream<DtoProdukt> produktStream(DtoPersonendaten person) {
    return Optional.of(person) //
        .map(DtoPersonendaten::getProduktListe) //
        .orElse(Collections.emptyList()) //
        .stream();
}

Die Einrückung unterhalb der "return"-Zeile wird vom Formatter automatisch hinzugefügt. Das ist auch der empfohlene Stil für Methoden-Verkettung, wie sie etwa für Object-Builder typisch ist: Jedes Statement in einer neuen Zeile.

Namenskonventionen

Die Java-Spezifikation macht nur wenig Einschränkungen, was die Wahl von Bezeichnern angeht. Grundsätzlich kann jeder Bezeichner – was auch immer er bezeichnet – aus einer nahezu beliebigen Folge von Unicode-Zeichen bestehen. Läßt man dieser Freiheit tatsächlich alle Zügel schießen, handelt man sich eine ganze Menge Probleme ein. Angefangen von der Verwechslungsgefahr und den unvermeidlichen Encoding-Problemen, bis zu der Frage ob der Bezeichner nun eine Klasse eine Variable oder einen generischen Typ bezeichnet. Namenskonventionen helfen, den Code auch ohne IDE-Unterstützung jederzeit flüssig lesen zu können.

Die Konventionen haben zwei Aspekte: Zum einen gibt es rein syntaktische Regeln, die üblicherweise mit regulären Ausdrücken beschrieben und von Sonar-Regeln überprüft werden können. Dazu gehört etwa die Regel, daß jede Methode mit einem kleinen Buchstaben beginnen soll. Diese Regeln sind leicht zu merken und zu befolgen. Zum anderen gibt es Regeln, die sich auf den Zweck des Bezeichneten beziehen und Informationen in den Code hineintragen. Dazu gehört die Regel, daß Klassen mit Substantiven bezeichnet werden sollen. Diese Regeln erfordern Kreativität vonseiten des Entwicklers. Sie können nicht mit mechanischen Regeln kontrolliert werden und führen bei Mißachtung zu besonders unangenehmen Mißverständnissen.

Allgemeines

Die Java-Spezifikation erlaubt die Verwendung aller Unicode-Buchstaben. Da Java-Source-Dateien aber keine Encoding-Informationen enthalten und bei vielen Zeichen-Kombinationen Verwechslungsgefahr besteht, wird die Menge der verwendbaren Zeichen eingeschränkt. Als Beispiel möge die folgende Methode dienen: {{{1}}} Kopiert man diesen Code in irgendeine Java-Klasse, giebt es in der letzten println-Zeile einen Compiler-Fehler. Das geschieht, weil das erste kleine "o" im Variablennamen kein russisches, sondern ein lateinisches "o" ist...

  • Es werden nur die lateinischen Buchstaben a-z, A-Z und die Ziffern 0-9 verwendet.
  • Umlaute und andere spezielle Buchstaben sind nicht erlaubt.
  • Der Unterstrich "_" wird nur für Konstanten verwendet.
  • Das Dollar-Zeichen "$" bleibt der internen Verwendung durch anonyme inner classes vorbehalten und wird nicht verwendet.

Für die Groß- und Kleinschreibungen gelten spezielle Regeln, es dürfen aber nie zwei Großbuchstaben hintereinander stehen (die einzige Ausnahme stellen die Konstanten dar), das gilt insbesondere für Abkürzungen. korrekt sind

HtmlRenderer renderHtmlPage ausgewaehlterKunde markTransactionForRollback

falsch sind

HTMLRenderer renderHTMLpage ausgewählterKunde

Generell soll jeder Bezeichner möglichst genau beschreiben, was das bezeichnete darstellt (Klassen, Interfaces), enthält (Konstanten, Variablen, Felder) oder tut (Methoden). Abkürzungen sollten grundsätzlich vermieden werden, bei fachliche getriebenen Abkürzungen sollte immer zwischen Nutzen und Klarheit abgewogen werden. Vor allem dürfen keine Mißverständnisse durch die Verwendung entstehen. Einzelne Buchstaben sind für Typ-Parameter vorgeschrieben. Bei Variablen sind sie grundsätzlich zu vermeiden. Für for-Schleifen-Zähler sind sie erlaubt und ggf. auch als Parameter für sehr allgemeine bzw. sehr kurze Methoden. Reservierte Worte wie void oder int können -- per Definition -- nicht als Namen verwendet werden, da beschwert sich schon der Compiler. Darüber hinaus sind einige spezielle Worte zu vermeiden die zwar nicht reserviert sind, aber eine ganz spezielle Bedutung haben. Es sind dies

  • var
  • record

Packages

Package-Namen enthalten ausschließlich kleine Buchstaben, die durch Punkte getrennt werden.

de.snakeoil.common.util.mock

Klassen

Klassen-Namen bestehen aus einer Folge von Worten, die eine substantivische Konstruktion bilden. Jedes neue Wort beginnt mit einem Großbuchstaben, alle anderen Buchstaben sind klein. Das gilt insbesondere für Abkürzungen: Html statt HTML. "Substantivische Konstruktion" bedeutet, daß der Klassen-Name als ganzes genommen einen Gegenstand, eine Idee oder ein Konzept bezeichnet, also z.B. einen "Kunden", einen "Builder", einen "Controller" oder ein "Entity". Durch zusätzliche Worte – und das müssen keine Substantive sein – kann die Natur der Klasse genauer spezifiziert werden. Zum Beispiel "PathBasedKeycloakConfigResolver".

Klassen-Namen beginnen immer mit einem Großbuchstaben richtig sind

FooErgebnisMapper HtmlRenderer CustomerFactoryBuilder

falsch ist

FOOErgebnisMapper htmlRenderer Customerfactorybuilder

Der Name einer Klasse, die eine Unterkasse von Exception erweitert – also eine Exception definiert – endet immer mit "Exception".

Der Name einer abstrakten Klasse (eine Klasse deren Deklaration der modifier "abstract" vorangestellt ist) beginnt immer mit "Abstract".

Interfaces

Interfaces werden nach den gleichen Regeln wie Klassen benannt. Bezeichnet das Interface eine Eigenschaft die ein Klasse haben kann, darf es auch mit einem englischen Adjektiv oder Adverb bezeichnet werden. Beispiele dafür sind z.B. die JDK-Interfaces Runnable und Closeable. Der Name beginnt in jedem Falle mit einem Großbuchstaben.

Es ist allgemein nicht mehr üblich, einem Interface-Bezeichner ein großes "I" voranzustellen. da man im Code normalerweise mit den Interfaces arbeitet und nicht mit den implementierenden Klassen, sollte das Interface den allgemeineren Namen tragen und die implementierenden Klassen den spezielleren.

Interface: PasswordService
Implementierung: PasswordServiceLdap
Implementierung: PasswordServiceDummy

oder – wenn nur eine einzige Implementierung existiert:

Interface: DataReaderAdapter
Implementierung: DataAdapterReaderImpl

Das Suffix "Impl" ist immer dann sinnvoll, wenn nur eine einzige Implementierung existiert, die Verwendung eines Interfaces jedoch zwingend erforderlich ist. Die Endung "Default" kann sinnvoll sein, wenn mehrere Implementierungen möglich oder vorgesehen sind, zunächst aber nur eine Implementierung erstellt wird.

Methoden

Methoden-Namen bestehen aus einer Folge von Worten, die eine verbiale Konstruktion bilden. Jedes neue Wort beginnt mit einem Großbuchstaben, alle anderen Buchstaben sind klein. Das gilt insbesondere für Abkürzungen: Html statt HTML. "verbiale Konstruktion" bedeutet, daß der Methoden-Name als Ganzes genommen eine Tätigkeit bezeichnet.

Methoden-Namen beginnen immer mit einem Kleinbuchstaben. z.B.

copyAddresse() run() renderHtmlPage()

Liefert die Methode einen Boolean-Wert, sollte der Name einer Aussage entsprechen, die wahr oder falsch sein kann:

enthaeltGueltigeAdresse() isEmpty()

Getter und Setter gehören weiterhin zum Standard-Repertoire der Java-Programmierung. Je nach Kontext kann der Zugriff auf eine Objekt-Eigenschaft, aber auch durch Methoden gestaltet werden die nicht der get-/set-Konvention entsprechen. Eine genaue Beschreibung steht noch aus, es ist jedoch bei der Wahl von Methoden-Namen immer darauf zu achten, daß keine Mehrdeutigkeiten entstehen. das gilt besonders für englischsprachige Bezeichnungen, da Substantive und Verben im Englischen nicht immer zu unterscheiden sind: Bezeichnet challenge(), daß die Methode eine Challenge liefert oder eine "Herausforderung" ausführt?

Variablen

Gemeint sind damit lokale Variablen, formale Methoden-Parameter und nicht-statischen Instanz-Variablen. Variablen-Namen bestehen aus einer Folge von Worten, die eine substantivische Konstruktion bilden. Jedes neue Wort beginnt mit einem Großbuchstaben, alle anderen Buchstaben sind klein.

Variablen-Namen beginnen immer mit einem Kleinbuchstaben.

Variablen enthalten Instanzen von Klassen und bezeichnen damit üblicherweise "Dinge" oder Instanzen von Ideen oder Konzepten. Dementsprechend werden sie ähnlich wie Klassen bezeichnet z.B.

requestHeader person gesuchte

https://docs.oracle.com/javase/tutorial/java/generics/types.html

Da – vor allem lokale – Variablen meist Dinge enthalten, die zu einem bestimmten Zweck gebraucht werden, sollte man sie dem Zweck entsprechend attributieren. Also besser "gesuchtePerson" statt "person" oder gar "p".

Konstanten

Konstanten sind in Java statische Felder in Klassen oder Interfaces. Konstanten-Namen beginnen mit einem großen Buchstaben enthalten nur große Buchstaben, Ziffern und das Unterstrich-Zeichen. Da nur große Buchstaben verwendet werden, dient der Unterstrich als Wort-Trenner z.B.

STEUERSATZ WARTE_ZEIT_NACH_ABLAUF_IN_TAGEN

enums

Java-Enums sind Klassen. Für den Namen der enum-Klasse gelten daher die gleichen Regeln wie für jeden Klassennamen. Für die Namen der enum-Instanzen gelten die Regeln für Konstanten-Namen – nur Großbuchstaben, getrennt durch Unterstrich.

Typ-Parameter

Typ-Parameter werden bei der Definition generischer Klassen und Methoden verwendet. Den von Oracle stammenden Regeln folgend werden Typ-Parameter durch einzelne, große Buchstaben bezeichnet. Hintergrund für diese spartanische Namensgebung ist, daß in der Regel nur wenige Typ-Parameter benötigt werden und diese sofort als solche erkennbar sein sollen und die Gefahr der Verwechslung mit anderen Bezeichnern möglichst gering gehalten werden sollte z.B.

public interface List<E> extends Collection<E>

Oracle empfiehlt, folgende Buchstaben zu verwenden:

Zweck typische Verwendung
E Element JDK-Collection-Klassen wie "List" verwenden das E
K Key Schlüssel, wie im Map-Interface
N Number
T, R Type allgemeiner Typ, wie im funktionalen Interface "Function"
V Value Wert, wie im "Map"-Interface
S, U, V Type Wenn T und R nicht genug sind.

Es ist empfehlenswert, für Typ-Parameter immer auch einen JavaDoc-Kommentar zu schreiben. Dazu wird das @param-Tag verwendet:

/**
 * @param <E> the type of elements in this list
 **/
public interface List<E> extends Collection<E>

Klassen-Aufbau

Um die Komponenten der Klasse schnell wiederzufinden, sollen sie immer in der gleichen Reihenfolge aufgeführt werden:

  1. Package-Deklaration
  2. Import-Statements
  3. Klassen- oder Interface-Deklaration
  4. Klassen-Variablen (statische Felder)
  5. Instanz-Variablen
  6. Konstruktoren
  7. Methoden

Die Reihenfolge der import-Deklarationen wird durch die Eclipse-Funktion "organize imports" sichergestellt. Innere Klassen folgen dem gleichen Aufbau und werden deklariert bevor sie das erste Mal verwendet werden Variablen werden nach Sichtbarkeit sortiert: public – protected – private. Für "package visible" Variablen gibt es keine Vorgabe, sie können nach dem "public"- oder derm "protected"-Block stehen. Die Methoden werden nicht nach Sichtbarkeit, sondern nach funktionaler Zusammengehörigkeit gruppiert. private Methoden werden üblicherweise nach der Methode aufgeführt, die sie als erste aufruft.

Kommentare

Das Erzwingen von Kommentaren – z.B. über Sonar-Regeln – führt in der Regel dazu, daß der Code große Mengen von Kommentaren enthält, die zwar formal korrekt, inhaltlich aber völlig nutzlos sind. Der völlige Verzicht auf Kommentare andererseits funktioniert nur dann, wenn sich der Codierer – insbesondere was die Wahl der Bezeichner anbelangt – besonders gewissenhaft um die Code-Qualität bemüht. Die folgenden Richtlinien versuchen einen Kompromiß zu finden, der einerseits den Entwickler nicht überfordert und andererseits die Kommentar-Qualität erhöht. Wie auch immer kommentiert wird, es gilt der eherne Grundsatz

Kommentar und Implementierung müssen immer miteinander in Einklang stehen

Allgemeines

Den JavaDoc-Standard zu forcieren ist nur dann sinnvoll, wenn die API-Dokumentation zur Anwendung generiert und verwendet wird. In der Praxis führt das nur zu einem "Rauschen" durch inhaltsfreie Kommentar-Skelette. Es ist aber sinnvoll, JavaDoc als Grundlage zu verwenden, Kommentare folgen daher der Struktur nach den JavaDoc-Konventionen. Das gilt insbesondere für Klassen- und Methoden-Kommentare:

  • Der Kommentar beginnt mit /** und endet mit **/, jede Zeile beginnt mit *
  • Die erste Zeile endet mit einem Punkt, gefolgt von einem <p>-Tag
  • Die Formulierung erfolgt immer in der dritten Person:
"Liefert den Wert..." und nicht "Liefere den Wert..."
  • Die Formatierung übernimmt der Formatter

JavaDoc-Kommentar-Skelette können in Eclipse schnell erzeugt werden durch Eingabe von "/**" und "Return"- Taste über dem zu kommentierenden Element. Diese Skelette müssen entweder ausgefüllt oder gelöscht werden. Sie dürfen nicht unverändert stehengelassen werden.

Muß

Das Minimum an Kommentaren, das gefordert wird.

Copyright-Vermerk

Am Anfang jeder Klassen-Datei steht – vor der package-Deklaration – der Kommentar mit dem Copyright-Vermerk und dem Jahr der Erstellung der Klasse

/*
 * Copyright (c) Swinging Pig Inc. 2019
 */

Klassen

Unmittelbar vor der Klassen-Deklaration steht der Klassen-Kommentar. in einer Zeile wird beschrieben, was die Klasse tut.

/**
 * Lädt über den gegebenen {@link RealmClient} die Konfiguration aus den Excel-Sheets.
 */
public class KonfigurationExcelFileLoader extends AbstractExcelFileLoader {

public und protected Methoden

Die public und protected Methoden bilden das Interface der Klasse. Jede Schnittstellen-Methode hat einen Kommentar. In einer Zeile wird beschrieben, was die Methode tut.

/**
 * Liefert den Wert des ersten Attributs vom angegebenen Typ in der Liste.
 */
public Optional<String> firstAttributValue(CustomerAttribute attributTyp) {

Ausgenommen von der Regel sind

  • Getter
  • Setter
  • Konstruktoren

wenn diese keine ungewöhnliche Logik enthalten, die dokumentiert werden sollte.

Deprectated-Tag

Ist eine Methode (Klasse, Feld, etc) mit @Deprecated annotiert, geht dem ein – mit @deprecated versehener – JavaDoc-Kommentar voraus, der beschreibt was anstelle der Methode zu verwenden ist.

/**
  * Konvertiert {@link LocalDate} nach {@link String} mit deutschem Format (dd.MM.yyyy).
  * <p>
  * NB: Im Unterschied zu {@link #asGermanDate(LocalDate)} fliegen hier exceptions.
  *
  * @param datum
  * zu konvertierendes {@code LocalDate}-Objekt
  * @return String mit Format dd.MM.yyyy
  * @deprecated use asGermanDate() instead
  */
 @Deprecated
 public static String toLocalString(LocalDate datum) {
     return datum.format(DateTimeFormatter.ofPattern("dd.MM.yyyy"));
 }

Leere Code-Blöcke

Jeder leere Code-Block enthält einen Kommentar, der angibt, warum der Block vorhanden ist.

private Convert() {
    // Instantiierung verhindern
}

Ausgenommen davon sind leere Lambda-Ausdrücke (dafür wird aber in der Regel besser eine Konstante definiert).

Kann

Bei Bedarf können Kommentare hinzugefügt werden, die über das obige Minimum hinausgehen. Wenn eine geänderte Implementierung den Kommentar überflüssig macht, ist das immer vorzuziehen.

Klassen

Neben der Beschreibungs-Zeile, können im Klassen-Kommentar Details beschrieben werden, die zur Klasse gehört.

  • Gebrauch der Klasse, Verwendung der Methoden (z.B. wann welche zu verwenden ist)
  • fachliche Motivation
  • Merkwürdigkeiten, ungewöhnliche Dinge in der Klasse

Was sich auf eine Methode bezieht, gehört in den Methoden-Kommentar.

Methoden

Neben der Beschreibungs-Zeile, können im Methoden-Kommentar Details beschrieben werden, die zur Methode gehören.

  • Parameter
  • fachliche Motivation
  • Merkwürdigkeiten, ungewöhnliches in der Implementierung

Parameter werden nach JavaDoc-Standard mit den entsprechenden Annotationen dokumentiert. Es gilt: entweder alle Parameter (und ggf. das Ergebnis) kommentierten, oder gar keinen. Unvollständige Dokumentation ist zu vermeiden.

/**
 * Bestimmt aus dem Code die Authentication-Session und daraus die redirect-URL.
 *
 * @param session
 * Keycloak-Session
 * @param code
 * der zu untersuchende Code
 * @return die redirect-URL aus der auth-Session zu der der code gehört oder {@code null}
 */
public static String getOriginalRedirectUriFromCode(KeycloakSession session, String code) {

private Methoden

private-Methoden können bei bedarf genauso kommentiert werden wie public- und private-Methoden, erforderlich ist das aber nicht.

Konstanten

Konstanten sollten durch den Namen ausreichend beschrieben sein. Ist nähere Erläuterung erforderlich, kann das "formlos" in einer Zeile darüber geschehen:

// UTC-Zone für die Konvertierung zonenloser Zeiten
private static final ZoneId ZONE_UTC = ZoneId.of("UCT");

Da in der Regel kein JavaDoc generiert wird, ist kein 3-zeiliger JavaDoc-Kommentar erforderlich; verboten ist das aber nicht.

Verboten

Kommentare sollen dem Code nur Informationen hinzufügen, die dem Code nicht inherent sind. was mit sprechenden Namen ausgedrückt werden kann (und auch ausgedrückt wird), wird nicht kommentiert.

  • Fehlerhafte Kommentare
  • Kommentare zu nicht existierenden Parametern
  • Kopierte Kommentare
  • ...
  • redundante Kommentare
  • Kommentare, die Methoden-Namen in Prosa wiederholen
  • Sätze wie "Standard-Konstruktor"
  • getXyz() → "Setzt das xyz-Feld"
  • auskommentierter Code (vor Übergang in die STU löschen)
  • @param und @result Tags ohne erklärenden Kommentar
  • Trennungs-Konstrukte (z.B. // -------------------)
Das "Clean Code"-Buch verbietet solche Konstrukte, weil die Klasse ja offensichtlich zu groß ist.
Das muß aber IMHO nicht zu strikt gehandhabt werden
  • Das gleiche gilt für Kommentare, die nicht zu einer Methode oder einem Feld gehören.
Das macht man gelegentlich, um Methoden-Gruppen zu kommentieren