Am 19. Oktober 2017 wurde die Konferenz BaselOne erfolgreich durchgeführt. In 4 bis 5 parallelen Talks wurden 6 Sessions gehalten, verteilt auf Vor- und Nachmittag. Die Markthalle Basel bot dazu eine tolle Location für die Vorträge, aber auch für Verpflegung, Networking und Abschluss-Apéro.

In einem ersten Teil wurden drei der Referate kurz vorgestellt.
Cloud Native Java – Getting started
Christian Schwörer
Testing Java Code effectively
Andres Almiray
Konsequent funktionale Programmierung für die JVM
Dierk König
In diesem zweiten Teil werden zwei weitere Referate vorgestellt.
Gradle 4.0 Scalable Builds
Etienne Studer zeigt in seinem Referat, wie Gradle grundsätzlich funktioniert und welche Features laufend umgesetzt und verbessert werden mit dem Ziel, die benötigte Zeit für den Build zu reduzieren.
Der Grandle Build wird in zwei Phasen ausgeführt: im ersten Schritt, der Konfigurationsphase, werden die Grandle Tasks und deren Reihenfolge bestimmt. Daraus resultiert der Build Task Graph (DAG). Im zweiten Schritt, der Ausführungsphase, wird der Task Graph ausgeführt. Gradle nutzt dabei eine Reihe von Optimierungen:
- Incremental Build: Typischerweise ändern zwischen den einzelnen Builds nicht alle Sourcen. Ziel ist es, nur die wenigen Änderungen neu zu bauen und die übrigen Outputs aus früheren Builds wieder zu verwenden. D.h. ein Task wird nur ausgeführt, wenn der Task Input geändert hat. Zum Input gehören Source Files, Compiler Flags etc., zum Output z.B. Class Files. Gleicher Input führt immer zu gleichem Output und muss deshalb nicht wiederholt werden.
- Build Cache: Damit der Output von Tasks wiederverwendet werden kann, können die Outputs von einem Build gespeichert werden, sowohl lokal als auch remote. Anhand des Inputs wird ein Cache Key berechnet, unter welchem der Output gespeichert wird. Bevor nun ein Task ausgeführt wird, wird zuerst im Cache geschaut, ob unter diesem Cache Key bereits Output vorhanden ist. Ist dies der Fall, muss dieser Task nicht erneut ausgeführt werden.
- Compile Avoidance & Incremental Compiler: Diese Optimierung berücksichtigt die Abhängigkeit von Interface und Implementierung: Es wird nur erneut compiliert, wenn das ABI (Application Binary Interface) geändert hat. Und es werden nur Klassen compiliert, welche davon betroffen sind. Bleibt das ABI unverändert (d.h. es ändert nur die Implementierung), so müssen nicht alle Abhängigkeiten neu compiliert werden.
- Gradle Deamon: Um das zeitaufwändige Aufstarten von Build-Prozessen zu verhindern, verwendet Gradle einen Background Process. Sobald ein Build angestossen wird, übernimmt der Deamon die Ausführung. Zudem können Informationen über Projektstrukturen, Files und Tasks im Memory gehalten werden.
- Worker API: Grandle verwendet ein API, um Actions parallel und sicher auszuführen. Parallele Actions können den Shared State nicht verändern.
- General Performance Improvements: Hier sind Verbesserungen im Bereich der Konfigurationszeit, parallele Auflösung von Abhängigkeiten sowie parallele Ausführung von Tasks oder Actions zu erwähnen.
- Composite Builds: Idealerweise wird ein Monolith in mehrere Libraries / Repos aufgeteilt. Ein Bug Fix in einer Library führt zur Compilation der Library, nicht des ganzen Monoliths. Der aktuellste Stand der Libraries kann in Integration Builds bereitgestellt werden.
Gradle verfügt über eine neue Konsole und ist gut integriert in die verschiedenen IDEs (z.B. Eclipse, Visual Studio, IDEA). Aus der IDE können Projekte importiert und synchronisiert werden und Tasks, Tests oder Builds ausgeführt werden. Aus der IDE wird das Tooling API angesporchen, welches seinerseits den Gradle Deamon anstösst.
Der Gradle Profiler erlaubt das Profiling und Benchmarking von Gradle Builds. Nebst dem Gradle Build Scan können auch Tools wie JProfiler, YourKit, Java Flight Recorder, Honest Profiler, Linux Perf oder Chrome Trace eingebunden werden.
Weiterführende Links:
Von API Design und glücklichen Usern
Ein Problem, das jeder aus seinem eigenen Entwickler-Alltag kennt: die meisten APIs sind nicht ansprechend gestaltet, wenig intuitiv, nicht selbsterklärend, inkonsistent… Doch APIs, gestaltet aus Sicht des späteren Anwenders, lassen sich besser verwenden, sind einfacher verständlich und machen mehr Spass ihrer Entwicklung und Nutzung.
Durch einfache Ansätze wie “Am Anfang steht das Interface” zeigen Christoph Engelbert und David Sondermann, wie bessere APIs gestaltet werden können.
- Interface aus Sicht des Anwenders: Es macht Sinn, das Interface aus Sicht des Anwenders, nicht aus Sicht des Entwicklers zu definieren. Erst wenn man aus Sicht des Anwenders mit dem Interface zufrieden ist, werden im zweiten Schritt Tests dafür implementiert, die das erwartete Verhalten des Interface definieren. Bereits hier zeigt sich, ob das Interface anwenderfreundlich ist. Mit Mocks kann das Verhalten getestet werden. Erst im dritten Schritt werden die Mocks nach und nach durch die eigentliche Implementierung ersetzt. Und steht einmal das Interface fest, kann die Implementierung einfacher parallel entwickelt werden.
- Verwendung von Standards: Anstatt das Rad immer wieder neu zu erfinden, kann eine Recherche im Internet helfen, eine entsprechende Bibliothek zu finden. Häufig existieren bereits Standard-Lösungen für viele Problemstellungen – wieso also nicht auf einer vorhandenen Lösung aufbauen?
- Fluent Interface: Immer mehr verbreiten sich Fluent Interfaces, welche einfach anzuwenden und zu lesen sind. Dabei muss beachtet werden, dass immer nur Interfaces und keine Implementierungen als Rückgabeparameter definiert werden. Weiter wichtig ist, dass man sich an ein klares Namensschema hält, um die Lesbarkeit zu erhöhen. Ein gutes Beispiel dazu ist das SQL Interface von https://www.jooq.org/
- Builder Pattern: Die gleiche Richtung wird mit dem Builder Pattern verfolgt wie mit den Fluent Interfaces: Anstelle einer bald einmal unüberschaubaren Anzahl von Konstruktoren und Parametern erlaubt es das Builder Pattern, komplexe Objekte Schritt für Schritt zu definieren und am Schluss zu instanzieren. Als willkommener Nebeneffekt erhält man damit die Möglichkeit, Immutable Objects zu erstellen. Wie bei Fluent Interfaces werden bei (verschachtelten) Builder Pattern wenn immer möglich Interfaces als Rückgabeparameter definiert.
- Namensgebung: Wie schon beim Fluent Interface erwähnt, ist ein klares Namensschema für den Anwender des Interface sehr hilfreich. Was auf den ersten Blick trivial erscheint, wird beim Beschreiben von technischen, standardisierten Begriffen (getXxx, setXxx, XxxFactory, XxxServiceBean) und fachlichen Begriffen (Netz, Dispo-Teilfahrplan, Anschlussgeberzug) plötzlich zum Stolperstein. Was ist nun richtig oder was wird nun konsequent verwendet: getNet, getNetz, gibNetz? Standards (JavaBeans) und Coding Conventions (Oracle, Google, Apache oder selber definierte Regeln) helfen hier weiter.
Weiterführende Links:
- https://en.wikipedia.org/wiki/Fluent_interface
- https://martinfowler.com/bliki/FluentInterface.html
- http://www.oracle.com/technetwork/java/codeconventions-135099.html
- https://google.github.io/styleguide/javaguide.html
- https://portals.apache.org/development/code-standards.html