Der neue Releasezyklus von Java

Seite 2: Java verändert sich zu schnell

Inhaltsverzeichnis

Eine häufig geäußerte Befürchtung ist, dass sich Java in Zukunft zu schnell entwickeln könnte und Entwickler sich schwer damit tun, alle Features kennenzulernen und ausreichend sicher zu beherrschen. Darin steckt die implizite Annahme, dass die kommenden Versionen ähnlich viel Neues bringen wie zuletzt Java 8 und 9. Wie man an Java 10 und 11 erkennen kann, ist das nicht der Fall. Beide Versionen enthalten nur wenige Features, die für den Entwicklungsalltag relevant sind.

Bei näherem Hinsehen ist das nicht weiter überraschend. Die kritische Ressource bei der JDK-Entwicklung sind die Entwickler selber, und Oracle hat das Team nicht vergrößert. Was sich ändert, ist also nicht die Entwicklungsgeschwindigkeit (zumindest nicht wesentlich), sondern die Geschwindigkeit, in der das Team Änderungen ausliefert. Features kommen nicht schneller, nur regelmäßiger (Java 11 ist ein gutes Beispiel dafür). In Mark Reinholds Worten: "The rate of innovation doesn't change. The rate of innovation delivery increases."

Warum die Einschränkung, dass sich die Entwicklung nicht wesentlich beschleunigt? Die Einschätzung des Autors ist, dass Java tatsächlich etwas flotter wird. Zum einen geht das Projekt Amber einige kleinere Sprachfeatures an, die wesentlich zügiger fertigzustellen sind als Mammutprojekte wie Lambdas, das Modulsystem oder Value Types. Zum anderen erwecken einige von Oracles Entscheidungen in der jüngeren Vergangenheit den Eindruck, dass sie Ressourcen von Wartung auf Weiterentwicklung umschichten.

Wer das Update auf Java 9+ schon hinter sich hat, weiß, dass das gerade bei großen Projekten kein Zuckerschlecken ist. Erwartet Entwickler das jetzt alle sechs Monate? Nein, zum Glück nicht.

Das Update auf Java 10 ist trivial, und auch Java 11 macht kaum mehr Probleme (falls man die Java-EE-Module bei der Migration auf 9 ersetzt hat). Oracle legt, genau wie in der Vergangenheit, weiterhin viel Wert auf Abwärtskompatibilität. Übrigens: Viele Änderungen, die bei der Java-9-Migration Probleme machen, sind penibel gesehen keine Inkompatibilitäten, weil sie nicht standardisierte Implementierungsdetails betreffen.

Kompatibilität ist aber nicht absolut und steht in einem Spannungsfeld mit der Evolution der Sprache, ihrer VM und APIs. Im Spannungsfeld wird Java sich tatsächlich ein bisschen aus der Kompatibilitätsecke heraus trauen:

  • Seit Java 9 hat die @Deprecated-Annotation ein Attribut forRemoval, das den Status true einnehmen kann. Die Entwickler können APIs also neuerdings nicht mehr nur als deprecated kennzeichnen, sondern auch entfernen.
  • Alle Garantien "bis zur nächsten Hauptversion", zum Beispiel was den Zeitraum der Entfernung von mit @Deprecated(forRemoval=true) annotierten APIs betrifft, gelten nicht mehr etwa 36 Monate sondern, nur noch sechs.
  • Jede Hauptversion zählt das Bytecode-Level hoch, was bei Bibliotheken wie ASM, die Bytecode manipulieren, immer wieder zu Problemen führt. Insbesondere Build-Tools sowie Frameworks wie Spring und Hibernate setzen solche Bibliotheken ein. Entwickler müssen sich auf regelmäßige Updates einstellen, zumindest bei der Abhängigkeit auf die jeweilige Bibliothek.
  • Man muss sich zwar etwas ins Zeug legen, um mit Incubator Modules und Preview Features Kompatibilitätsprobleme zu erzeugen, aber unmöglich ist es nicht.

Zukünftige Migrationen sind also bei weitem nicht so schwierig wie die zu Java 9, aber die derzeit gelegentlich verbreitete Einschätzung, dass der Schritt von Java 10 auf 11 so unkompliziert ist wie der von 8u20 auf 8u40, ist nicht ganz richtig. Es ist allerdings möglich, Migrationen durch verschiedene Maßnahmen zu vereinfachen.

Eine naheliegende Maßnahme ist, sich unbedingt nur auf standardisierte Features, unterstützte APIs und festgelegtes Verhalten zu verlassen. In den meisten Fällen merkt man, wenn man vom diesem Weg abweicht, aber solche Abhängigkeiten können subtil sein und versehentlich entstehen.

Klassisches Beispiel ist die Reihenfolge, in der die Reflection API die Methoden einer Klasse zurückgibt. Als das in einem kleinen Java-Update geändert wurde, scheiterten plötzlich überall Tests, Persistenzschichten und anderer Code, der versehentlich von der Aufrufreihenfolge annotierter Methoden abhängig war.

Um sich nur auf standardisiertes Verhalten zu verlassen, müssen Entwickler die Augen offen halten, wobei Entwicklungsprozesse wie Pair Programming und Code Reviews helfen können. Außerdem sollte man sich bei wichtigen Entscheidungen, die das Aussehen des Codes langfristig prägen, nicht nur auf beobachtetes Verhalten verlassen, sondern unbedingt in den dazugehörigen Vertrag schauen, also meist die Java Language Specification beziehungsweise Javadoc.

Eine andere Maßnahme ist, bei den eigenen Abhängigkeiten genau hinzuschauen, wie gut das jeweilige Projekt gewartet wird. Es lohnt sich, bei Codebasen, die lange leben sollen, Projekte zu bevorzugen, an denen Entwickler aktiv arbeiten und die viele Nutzer haben. Das macht es wahrscheinlicher, rechtzeitig Updates zu bekommen, die Migrationsprobleme lösen. Dass dieses Vorgehen die Experimentierfreudigkeit dämpft, ist allerdings ein unangenehmer Nebeneffekt.

Die dritte Maßnahme lautet ganz einfach: Updates. Abhängigkeiten, Entwicklertools, Infrastruktur – alles jederzeit auf der aktuellen Version zu halten ist aufwendig, aber bei Java-Updates macht sich das bezahlt. Wer zunächst alle Abhängigkeiten aktualisiert, bevor man Java auf die nächste Version hebt, kann manche Hürde überspringen, ohne sie überhaupt zu bemerken.

Wer Anwendungen zum Kunden ausliefert und sich dort mit verschiedenen Java-Versionen konfrontiert sieht, sollte jlink in Betracht ziehen. Mit dem in Java 9 hinzugefügten Werkzeug lässt sich ein komplett eigenständiges Image erstellen, das den eigenen Code, alle Abhängigkeiten sowie das JRE enthält. Dann spielt es keine Rolle mehr, ob und welche Java-Version auf der Zielmaschine installiert ist, und man kann sich ganz auf ein Release konzentrieren.

Im Gegenzug gibt man die Unabhängigkeit vom Betriebssystem auf (jedes Betriebssystem erfordert ein eigenes Image), und wenn auf dem Zielrechner das JRE aktualisiert wird, profitiert man nicht von dessen verbesserter Sicherheit oder Performanz. Nominell muss man Module erstellen, um jlink zu verwenden, aber Tools wie Moditect erlauben, das zu umgehen.