Skip to content

Entwicklung: Programmierrichtlinien

Nicklas Wiegandt edited this page Sep 20, 2017 · 3 revisions

Code-Stil

Es wird der Google Java Style genutzt. Für Eclipse und co können entsprechende Einstellungen hier gefunden werden: GitHub - Google Styleguide

Warum der Google-Stil? Nun, er ist einer der wenigen Java-Styles, der nicht nur teilweise, sondern durchgängig (d.h. in ziemlich allen Belangen) definiert wurde. Daneben wurde er auf Lesbarkeit optimiert, und nicht auf althergebrachte Probleme. Tabs haben etwa den Vorteil, individuell einstellbar zu sein und brauchen weniger Platz. Das Problem bei Tabs entsteht aber letztlich bei der Ausrichtung (nicht Einrückung, sondern alles nach dem ersten Zeichen). Daneben ist das Argument, dass ein Tab nur ein Byte einnimmt, heutzutage nicht mehr sehr wesentlich.

Bleibt die Frage, warum mit zwei (2) Spaces eingerückt wird. Die Frage ist einfach zu beantworten: Wesentliche Elemente stehen untereinander.

if (condition) {
  doThis();
} else {
  doThat();
}

Einrückung und Klammern

Es wird der Google Java Style genutzt. Das heißt im Wesentlichen:

  • Klammern auf gleicher Ebene.
  • Einrückung: Zwei Spaces. Keine Tabs.
  • Konstanten (static final) an den Anfang, in Großbuchstaben.
  • null wird nicht genutzt.
  • Imports werden alfabetisch sortiert. Ausnahme sind die Projekteigenen Klassen -- die stehen am Anfang.
  • else ist zu vermeiden.
  • Es sind nicht mehr als drei Einrückungsebenen zu nutzen (kein zu tiefes verschachteln von if/for/while etc.).

Folgende Änderungen sind vorzunehmen:

  • Special Imports: Hier kommen die Projekteigenen Pakete hin.
  • Zeilenlänge: Wird von 100 auf 140 Erweitert.

Zum Thema else: Oft kann man else vermeiden, indem man etwa mit Preconditions und/oder frühen if-Abfragen auf negativ-Bedingungen aus der Methode herausspringt. Der eigentliche Code steht dann wieder auf der Haupt-Einrückungsebene.

Zeilenumbrüche / Leere Zeilen

Folgende leere Zeilen werden eingefügt:

  • Vor einem return
    • Ausnahme: Es ist das einzige Statements eines Blockes ({ return; }).
  • Vor einem if und nach der schließenden Klammer }.
  • Genauso bei for, catch und while.
  • Variablen oder ähnliche Statements können nach Bedarf durch Leerzeilen visuell gruppiert werden.

Speichern und Ändern von Code

Alter Code wird im alten Stil belassen, um Pull-Requests und Commits auf das nötigste zu reduzieren.

Pakete

  • Neue Pakete werden unter de.mediathekview angelegt.
  • msearch-Pakete unter de.mediathekview.msearch.
  • mserver-Pakete unter de.mediathekview.mserver.
  • etc.
  • Das ist für das Logging und für das Veröffentlichen nach Maven von Bedeutung.

Klassennamen

  • Statt HTMLDriver schreibt man nach Google-Konvention HtmlDriver. Statt ZDFConnector entsprechend ZdfConnector.

Exceptions

  • Checked exceptions (extends Exception) werden nur mit Begründung genutzt. Ansonsten werden unchecked exceptions genutzt (extends RuntimeException).

Checked Exceptions sollten da benutzt werden, wo man gut darauf reagieren KANN und wo es sinnvoll ist. Etwa, bei einer fehlenden Konfiguration (neue wird erstellt), ggf. HTTP-Timeouts (neuer Versuch, wobei bei Lambdas und CompletableFuture das wieder eine unchecked exception wäre).

Bei anderen Dingen, die wir dann nicht mehr kontrollieren können, sollten Exceptions aber nicht die Methodensignatur "beschmutzen". Das führt zu unnützen throws-Ketten in der call-Hierarchie oder zu unsinnigen Catches (log.debug("Fehler bekannt, aber kann man eh nichts machen.", e);). Und das sind, wenn man einen solchen Interceptor verwendet, erfahrungsgemäß die meisten Situationen.

Funktionale Programmierung mit Lambdas und co

Mit Java 8 sind Lambdas und co gekommen wodurch es möglich ist einige dinge zu verkürzen und zu beschleunigen. Diese Möglichkeit der funktionalen Entwicklung sollte auch für genau das genutzt werden. Beschleunigung und Verkürzung. Z.B. zum schnellen und kurzen sortieren einer Liste. Größere Aufgaben sollten nicht funktional umgesetzt werden da dies u.a. die Lesbarkeit drastisch verschlechter. Also kein aufruf von komplexen Methoden o.ä. innerhalb von Lambdas und co.

null etc.

  • Eine Methode gibt niemals null zurück. Ausnahmen sind alter Code.
  • Besondere Fälle müssen mit @Nullable annotiert werden. Javadoc ist dazu erforderlich.
  • Im Zweifel ist zu Prüfen, ob Optional oder eine eigene Klasse mit einem "leeren" Status besser geeignet ist.
  • Gibt eine Methode eine Collection zurück, darf ebenfalls niemals null zurückgegeben werden. Bei keinen Ergebnissen gibt es eine leere Liste, bei Fehlern eine abgeleitete RuntimeException.
  • Parameter sollten nicht nullable sein. Ausnahmen werden gekennzeichnet.
  • Jede Methode sollte etwa mit Google Guava o.ä. mittels Preconditions.checkNotNull() den Status einer Variable prüfen.
  • Optional ist nicht Serializable. Als Feld kann es also mitunter nicht genutzt werden. Stattdessen sollten dann ggf. eigene Klassen verwendet werden.
  • Literatur:
  • https://github.com/google/guava/wiki/UsingAndAvoidingNullExplained
  • https://dzone.com/articles/java-8-optional-avoid-null-and

Value-Objekte

Sprache

Die Kommentarsprache ist Deutsch|Englisch [TBD].

Javadoc

  • An jeder public-Methode steht Javadoc.
  • An @Override-Methoden steht kein Javadoc. Auch kein IDE-Generiertes @see oder inheritdoc, und erst recht kein non-javadoc.
  • Parameter werden beschrieben.
  • RuntimeExceptions, die in genau dieser Methode geworfen werden, werden an Javadoc annotiert, aber nicht deklariert.

Code-Analyse

Findbugs

Es werden Findbugs, findbugs-contrib und Findbugs-Security genutzt. Es werden alle Analysen eingeschaltet, mit Ausnahmen von:

  • CRLF Injection

PMD

  • Es werden keine Controversial-Prüfungen eingeschaltet.

Logging

  • Wird eine Exception nicht weitergeworfen, muss sie geloggt werden.
  • Ist absolut ausgeschlossen, dass der Block nicht erreicht wird, loggt man im Level Trace.
  • Der Logger wird immer so definiert: private static final Logger LOG = LoggerFactory.getLogger(Class.class);.
  • Das Logging-Framework ist Log4J

Tests

Für jeden gelösten Issue sollte ein JUnit-Test existieren.