Kolumne: EnterpriseTales

Schicksalsfrage: Muss es immer Java sein?
Keine Kommentare

Als Programmierer versuchen wir Software zu schreiben, um unsere spezifischen Probleme zu lösen. Wo einst maschinennahe Programmiersprachen wie C++ dominierten, erhoben sich Java und dessen Sprachdesign zu einer echten Alternative. Mit der Zeit haben sich jedoch Umwelt und Erfahrung verändert. Neue Sprachen, wie z. B. Kotlin, haben sich dank Google und JetBrains in der breiten Masse etabliert. Doch vor welchen Problemen steht Java heute und wie kann Kotlin überzeugen?

Java und seine Stärken

Eigentlich sollten sich Programmierer darauf konzentrieren, Businesslogik umzusetzen. Dazu sollten sie sich weniger mit technischen Herausforderungen der jeweiligen Programmiersprache auseinandersetzen müssen. Maschinennahe Sprachen sind für Geschäftsanwendungen deshalb meistens eher hinderlich, da sie eine gewisse Komplexität mitbringen. Auch deshalb erlebte Java Anfang dieses Jahrtausends einen regelrechten Hype. Verbessertes Pointer Handling, Standardisierungen in Verbindung mit dem Web, die Plattformunabhängigkeit und eine vollständige Rückwärtskompatibilität und damit verbundene Investitionssicherheit machten die Sprache unter anderem interessant für Unternehmen.

Durch Javas Verbreitung entstand eine riesige Community. Aus dieser ergab sich in der Folge ein Ökosystem mit einer Vielzahl von nützlichen Bibliotheken und Programmiersprachen rund um die Java Virtual Machine (JVM).

Doch so wie Java Spracheigenheiten von C++ verbessert hat, sind bei der Nutzung von Java selbst über die Jahre neue praktische Erfahrungen entstanden. Grundlegende Designentscheidungen, wie z. B. der „Billion Dollar Mistake“ (die Null-Referenz), die im Nachhinein hätten anders getroffen werden sollen, sind aufgrund der Rückwärtskompatibilität nur schwer wieder rückgängig zu machen. Auch Unternehmensentscheidungen und Hypes wie der Weg zum Cloud-Computing – das Auslagern von Infrastruktur an externe Anbieter, das Auftrennen von Software in kleine Dienste (Microservices) oder der Wegfall von klassischen Desktopapplikationen hin zu Single Page Applications (Applikationen, die vollständig im Browser laufen) beeinflussen die Technologiestacks heutzutage stark. Dazu kommen Konzepte und Methoden der Softwareentwicklung selbst, wie das Domain-driven Design, die Verwendung von bestimmten Entwurfsmustern oder der immer weiter ansteigende Anteil funktionaler Konzepte im Sprachdesign. Programmiersprachen unterliegen deshalb gewissen Anforderungen von Entwickler- und Unternehmensseite.

Dynamik in der Umgebung

Die Trends von damals haben sich im Gegensatz zu denen von heute verändert. Das Web, Microservices, Single Page Applications und Mobile-Applikationen sind der Quasistandard geworden und neue Problemfelder wurden aufgerissen. Applikationen sind heute häufig verteilte Systeme, die unterschiedliche Programmiersprachen verwenden. Eine gemeinsame Verwendung von Code zwischen den Codebasen ist damit oft kaum möglich. Mit dem Wachsen des Webs ist z. B. die Sprache JavaScript bis dato unumgänglich – ein Problem, wenn nicht die gesamte Landschaft aus JavaScript bestehen soll. Validierungslogik und Schemata der Daten werden deshalb häufig – bewusst oder unterbewusst – in verschiedenen Sprachen doppelt implementiert. Wird beispielsweise eine Single Page Application entwickelt, die ein Feld auf eine bestimmte Regular Expression prüft, muss diese Logik im Client in der Sprache JavaScript implementiert werden, um dem Benutzer frühes Feedback geben zu können. Da eine Grundregel „never trust the client“ lautet, muss die gleiche Logik aber nochmal auf dem Server implementiert werden. Ändern sich die Validierungsregeln, müssen beide Systeme angepasst werden.

Eine Lösung speziell für das Web schien das „Google Web Toolkit“ (GWT) zu sein. Mit dem GWT war es erstmals möglich, Single Page Applications in der Programmiersprache Java statt JavaScript zu schreiben. Ein tieferes Verständnis von JavaScript war für die Hauptentwicklung zunächst nicht nötig. Code konnte zwischen Client und Server geteilt werden. Das Framework brachte jedoch eigene Probleme mit sich, und die Entwicklung des Frameworks steht heute im Prinzip still.

Zu einer Single Page Application gehört in den meisten Anwendungen auch ein Backend, das auf einem Server gehostet werden muss. Immer weniger Unternehmen wollen jedoch eine eigene Infrastruktur pflegen und kaufen sich damit in Dienste und Infrastruktur der Cloud-Anbieter ein. Diese wird häufig nach in Anspruch genutzter Rechenzeit bezahlt. Die proprietären Dienste kommen aber selbst mit Einschränkungen.

Applikationen, die die Logik der Dienste abbilden, müssen bei Bedarf hoch- und wieder herunterskaliert werden können, sodass das Initialisieren der Laufzeitumgebung möglichst wenig Zeit in Anspruch nehmen sollte, um Kosten für die genutzte Zeit zu sparen. Skriptsprachen, wie beispielsweise JavaScript, oder kompilierte Sprachen können hier durch ihre Laufzeitumgebung punkten. Während Java auf einer vergleichsweise langsamen JVM ausgeführt werden muss, kann Kotlin nach JavaScript oder sogar nativ kompiliert werden und so Ausführzeit sparen.

Es lässt sich erkennen, dass mittlerweile in einer Sprache neben ihrer Ausdrückbarkeit und Performance deshalb vor allem die Flexibilität der Umgebung interessant ist.

Für Java wurden für moderne Probleme oft Lösungen in Form von Frameworks und Technologien gefunden. Die Plattformunabhängigkeit ergab sich dadurch, dass die JVM auf allen Plattformen verfügbar war. Was aber, wenn das nicht der Fall ist, wie z. B. im Browser? Bestimmte Probleme wurden dann mit Frameworks angegangen, andere gar auf einer viel tieferen Ebene – der Runtime-Ebene – wie die heutige Entwicklung von GraalVM zeigt.

Im Gegensatz zu Java geht die Programmiersprache Kotlin einen anderen Weg. Die Sprache Kotlin ist selbst die Schnittstelle zwischen den Plattformen. Sie ist nicht nur für den ausschließlichen Einsatz auf der JVM bestimmt. Bei Kotlin stehen dem Entwickler mehrere Kompilierziele zur Verfügung – neben der JVM sind das native Mobile-Umgebungen oder auch JavaScript. Natürlich darf man dabei nicht vergessen, dass sich viele dieser Kompilierziele noch im Betastadium befinden. Doch dadurch, dass die Sprache so flexibel in ihrer Ausführung ist, kann sie hervorragend genutzt werden, um Kernlogik zu implementieren. Der Code kann dann, sofern er nicht auf spezifische Features der Umgebung zugreift, geteilt genutzt werden. In der Regel ist es allerdings nicht nötig, solche bei der Implementierung von Businesslogik und Validierung zu verwenden.

Developer Experience und Domain-driven Design

Die Flexibilität einer Sprache für das Unternehmen ist eine Sache, der praktische Umgang damit, die Developer Experience, eine ganz andere. Um wartbaren Code zu schreiben, ist es uns als Softwareentwickler natürlich wichtig, die Logik möglichst kurz und präzise zu implementieren. Dabei sollten wir dafür sorgen, dass die Validität verschiedener Objekte jederzeit gewährleistet ist, um unser Programm frei von Bugs zu halten. Ein Grund, warum das Umsetzen von Domain-driven Design im Code so gut funktioniert ist, dass das Umsetzen von Domänenlogik in Werteobjekten und Entitäten führt dazu, dass das Typsystem logische und fachliche Fehler schon zur Compile-Zeit abfängt, wenn wir z. B. versuchen, ein Alter auf einen Geldbetrag zu addieren.

Für solche Fälle abstrahieren wir Entwickler häufig in sogenannte Werteobjekte [1], also Objekte, die Werte und verbundene Invarianten der Modellierung enthalten. Damit wir diese Werte sicher zwischen den Objekten hin- und herreichen können, sollten diese in der Regel unveränderbar sein und sich über ihre Werte statt über ihre Identität identifizieren. In Java erreichen wir das, indem wir equals und hashCode sowie toString implementieren. Dazu können wir die Felder theoretisch final deklarieren. In der Praxis ist das aber in Verbindung mit den diversen Frameworks der Sprache nur schwer möglich. Der erste Schritt ist dann meistens, diese Funktionalitäten in eine generische Basisklasse auszulagern. Nicht selten folgen wilde Vererbungshierarchien.

Und hier helfen auch schon die Kotlin Data Classes, die sich auf das Konzept von unveränderlichen Werteobjekten auf Sprachebene abbilden lassen. Sie implementieren die zuvor erwähnten Methoden – und zwar vollautomatisch. Dabei ist besonders angenehm, dass Klassen in Kotlin standardmäßig final sind. Lange Vererbungshierarchien werden damit vermieden.

Doch mit Wertobjekten allein ist es nicht getan. Denn auch wenn die Fachlichkeiten gut modelliert wurden, können sie immer noch falsche Typen haben. Namentlich den Null-Typ. In Java ist die Existenz eines Werts immer optional. Zwar kann die Existenz von Pflichtfeldern hier durch Laufzeitvalidierung und eine hohe Testabdeckung größtenteils sichergestellt werden, eine Garantie ist das aber nicht – vor allem nicht in Teams, die sich aus Mitgliedern mit verschiedenen Erfahrungsspektren zusammensetzen.

In Listing 1 wird die beispielhafte Modellierung einer Person gezeigt. Diese besitzt einen Namen, der wiederum einen Vornamen enthalten kann.

class Person {
  // ...
  public String getName() {
    if (this.getName() != null) {
      return this.getName().getFirstname();
    }
    return null;
  }
};

Dabei ist der Typ der Methode aus der Signatur kaum zu erkennen. Die Signatur gibt zwar an, einen String wiederzugeben, dass dieser Null sein kann, sieht der Entwickler ohne Implementierung aber nicht. Abhilfe kam hier mit Java 8, als vermehrt funktionale Programmierkonstrukte in die Sprache fanden, z. B die Optional-Monade. Diese wird häufig zur Markierung von optionalen Typen verwendet. Das Problem ist natürlich, dass auch eine Variable vom Typ Optional null sein kann und das Grundproblem der Sprache damit nicht löst. Das führt dazu, dass in fremden Codebasen schnell defensiv, also mit sehr vielen unnötigen Null-Checks, programmiert wird. Eine steigende Komplexität in der Codebasis ist die Folge.

class Person(val name: Name?){
  fun getName() = name?.firstname 
};

Und genau dieses Problem existiert bei Kotlin nicht. Bei Kotlin wird explizit zwischen vorhandenen Typen und optionalen Typen unterschieden. Der Umgang damit wird außerdem erleichtert und der Code wird simpler. Vergleicht man dazu die Signatur oben mit der aus Listing 1 ist schon in der Signatur bekannt, dass der Name einen optionalen Wert enthält. Und es fällt noch etwas auf: Der Umgang mit optionalen Werten und der Sprache an sich scheint viel einfacher. Nicht nur, dass die Null-Check-Kaskaden, die wir dem Java-Beispiel entnehmen können, durch ein einfaches Fragezeichen ausgetauscht wurden, der Code enthält auch viel weniger Zeilen, in denen Fehler passieren können. Kurz gesagt: Weil bei Java das Abfangen von Typfehlern von der Compile-Zeit in die Laufzeit verlagert wird, ist es anfälliger für Bugs. Eine strikte Testabdeckung ist deshalb Pflicht. Das führt leider zu mehr Aufwand in der Implementierung und damit höheren Kosten der Software.

Fazit

Die Welt steht nicht still und auch Technologien entwickeln sich weiter. Java hat lange Zeit als hervorragende Programmiersprache gedient. Doch wenn sich die Umgebung ändert und die Erfahrungen zunehmen, müssen sich auch die Sprachen adaptieren. Durch kürzere Releasezyklen und eine höhere Bereitschaft, neue Sprachfeatures aufzunehmen, wird zwar auch Java kontinuierlich an heutige Bedürfnisse angepasst, die Entwicklung wird aber durch das Mantra der Abwärtskompatibilität gehemmt.

Es ist klar zu erkennen, dass Kotlin aus den Erfahrungen von Java lernen konnte. Da Kotlin eine relativ junge Sprache ist, ist sie natürlich von Beginn an hervorragend an die heutige Zeit und ihre Probleme angepasst – vor allem im Hinblick auf Developer Experience. Generell ist das aber nur ein Ausschnitt möglicher Faktoren. Code, der geschrieben ist, muss häufig lange Zeit gewartet werden. Und auch wenn Kotlin gerade einen Aufschwung erlebt, ist Java immer noch weiter verbreitet und Entwickler sind leichter zu finden. Während Java sich beim Tiobe-Index wacker auf Platz 2 hält, ist Kotlin noch nicht einmal unter den Top 20 der populären Programmiersprachen zu finden.

Unternehmen brauchen häufig eines: Investitionssicherheit. Kotlin ist modern und es ist angenehm damit zu arbeiten. Java war und wird aber vermutlich, Standardisierungen sei Dank, noch lange Zeit am Markt bleiben. Das sind Erfahrungen, auf die Kotlin aufgrund seines Alters noch nicht zurückgreifen kann. Dass Kotlin aber sowohl vom Hause JetBrains als auch von Google gepusht wird, lässt auf Großes hoffen. Eine Frage bleibt also offen: Muss es immer Java sein?

Unsere Redaktion empfiehlt:

Relevante Beiträge

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu:
X
- Gib Deinen Standort ein -
- or -