Aus Liebe zu modernem Java
In diesem Artikel geht es nicht um die neueste Version von Java, sondern um die Entwicklung der Arten von Problemen, die wir damit lösen können: die Verwendung externer Dienste, der Umgang mit synchronen Aufrufen und die Handhabung vieler verschiedener Abhängigkeiten, die wir nicht kontrollieren können.
Als ich Java lernte, lernte ich etwas über Objekte und Vererbung, Schnittstellen und Implementierungen, Ausnahmen, synchronen Code, Debugging, Dependency Injection und die Kontrolle über Null. Die Chancen stehen gut, dass Sie Java auf ähnliche Weise gelernt haben.
Wenn Sie Glück haben und jünger sind als ich, hat Ihnen vielleicht auch jemand einige der neuen Funktionen erklärt, die seit Java 8 eingeführt wurden, wie Lambda-Ausdrücke, Streams oder Module. Aber selbst wenn Sie jung sind, bin ich mir ziemlich sicher, dass viele von Ihnen Ausnahmen werfen und Nullprüfungen durchführen. Sie programmieren also (noch) nicht auf funktionale Weise.
Wir sind seit Jahren daran gewöhnt, auf diese Weise zu kodieren, und haben nicht viel darüber nachgedacht, es anders zu machen. Vielleicht verwenden Sie einige Java 8-Funktionen, aber im Endeffekt verwenden Sie immer noch die üblichen Java-Muster. In diesem Blog möchte ich Sie dazu ermutigen, Ihre Art der Programmierung zu hinterfragen.
Vererbung
Java ist eine objektorientierte Sprache, daher sind zwei der ersten Dinge, die man lernt, Objekte und Vererbung. Zum Beispiel müssen Sie ein Objekt erstellen, um ein einfaches "Hallo Welt"-Programm zu schreiben.
Aber Vererbung ist weder zwingend noch kostenlos: Sie schaffen eine Abhängigkeit zwischen Ihren Klassen. Überlegen Sie also zweimal, bevor Sie eine Klasse erweitern, und ziehen Sie stattdessen die Komposition in Betracht.
Komposition ist das Konzept der Verwendung von Codeschnipseln aus anderen Klassen, ohne eine Abhängigkeit zwischen ihnen zu schaffen. Ein Beispiel für Komposition ist Dependency Injection.
Ich ziehe es jedoch vor, statische Funktionen oder die in Java 8 eingeführten Standardschnittstellen zu verwenden. Seien Sie jedoch vorsichtig mit statischem Kontext und versuchen Sie, jede Art von Zustand zu vermeiden. Es ist besser, alle Objekte an die Funktion zu übergeben, die Sie aufrufen.
Es gibt viele Artikel, die die Vorteile der Komposition gegenüber der Vererbung im Detail erklären, aber zusammenfassend kann ich sagen, dass Ihr Code besser wiederverwendbar ist, wenn Sie die Vererbung vermeiden.
Um den Wechsel von Vererbung zu Komposition zu vollziehen, sollten Sie sich fragen, ob Vererbung der beste Weg ist, um ein Problem zu lösen, zum Beispiel bei der Entwicklung einer Benutzeroberfläche. Sie müssen nicht radikal sein, denn Java erlaubt die Koexistenz von Vererbung und Komposition.
Funktionale Programmierung
Wenn Sie dies lesen und Ihren Java-Code modernisieren wollen, empfehle ich Ihnen, mit der funktionalen Programmierung zu beginnen. Bei der Verwendung von Java können Sie gleichzeitig objektorientiert und funktional entwickeln. Clevere Programmierung nimmt das Beste aus beiden Welten.
Bei der funktionalen Programmierung dreht sich alles um Zustandslosigkeit und die Weitergabe von Daten zwischen Funktionen: Unveränderlichkeit überall. Objekte und Daten können sich nicht ändern. Funktionen können Argumente in Methoden sein, zum Beispiel mit Lambdas. Ausnahmen sind nicht erlaubt und null existiert nicht.
Die Verwendung von Lambdas, Stream und Optional allein bedeutet jedoch nicht, dass Sie auf funktionale Weise entwickeln.
Um zu lernen, wie man auf rein funktionale Weise entwickelt, können Sie funktionale Sprachen wie Haskell, Elixir, Frege und Clojure in der JVM erkunden. Es gibt auch Bibliotheken in Java, die funktionale Funktionen wie Vavr bieten .
Ich gebe zu, dass funktionale Programmierung mit einer Kette von Funktionsaufrufen von map, flatMap und reduce schwierig sein kann. Aber haben Sie keine Angst vor dieser Veränderung. Es wird Ihren Geist öffnen und Ihr Code wird in vielerlei Hinsicht besser sein: wiederverwendbar, wartbar, lesbar und mit verbesserter Fehlerbehandlung.
Das Erstellen eines eigenen @FunctionalInterface und die Rückgabe von Optional anstelle des Werfens von Ausnahmen ist ein guter Weg, um mit funktionaler Programmierung zu beginnen.
External Code
In einer Welt der Microservices, REST-APIs, SaaS und AWS müssen wir mit Code umgehen, den wir nicht kontrollieren können.
Manchmal erhalten wir eine Java-Bibliothek, ein anderes Mal einen HTTP-Client, um mit diesen Diensten zu interagieren. In allen Fällen müssen wir eine Schnittstelle erstellen, und ich empfehle Ihnen, die folgenden Best Practices zu befolgen:
Vermeiden Sie es, ein Objekt oder eine Bibliothek, die nicht Teil Ihrer Codebasis ist, direkt zu veröffentlichen, um Probleme zu vermeiden, falls sich der externe Dienst ändert. Wenn Sie Ihre eigenen Klassen und Schnittstellen anbieten, können Sie Ihre Implementierung aktualisieren, anstatt Ihre Schnittstelle zu ändern, wenn sich der externe Dienst ändert.
Ihre Methoden sollten keine Ausnahmen auslösen, denn sonst müssten Sie die Ausnahme bei jeder Verwendung der Schnittstelle überprüfen. Ab Java 8 können Sie Optional verwenden.
Schließlich sollten alle Methoden einen Rückgabewert haben, und void ist nicht erlaubt. Bei einem asynchronen Aufruf ist es gute Praxis, CompletableFuture zurückzugeben.
Ich möchte Ihnen einige Codes zeigen, um sie zusammenzufassen:
public interface MyExternalService { //Schlechte Methodenbeispiele void save(Map object); any.external.Object getDetails(String something); Map savePoint(int x, int y) throws java.lang.IOException; //Gute Methodenbeispiele, Auto und Details sind Klassen, die wir kontrollieren Optional<Details> getDetails(); CompletableFuture<Car> save(Map car); }
Managing hundreds of code branches with Jenkins
Maintaining hundreds of Java modules with active releases can be a challenge. Read our blog to learn how a modular approach based on Jenkins pipelines can help you build, test and deploy artifacts flexibly.
Fehlerbehandlung
Wenn Sie die bisher von mir vorgeschlagenen Änderungen umgesetzt haben, sind Sie auf einem guten Weg und werden viel weniger unter der Fehlerbehandlung leiden, die typischerweise aus diesen 4 Situationen resultiert:
Unbehandelte Ausnahmen
Methoden
- die Sie aufgerufen haben
- ohne ihren Rückgabewert zu überprüfen
Objekte
- die sich nicht in dem Zustand befinden
- den Sie erwarten
Null-Prüfungen
Um diese zu vermeiden, entfernen Sie Ausnahmen und geben Sie Optional-Ergebnisse zurück. Wenn Optional nicht ausreicht, weil Sie mehr Informationen über den Fehler benötigen, verwenden Sie eine Either-Klasse, eine Klasse mit zwei Eigenschaften: das Ergebnis und den Fehler. Es gibt keine Either oder Pair Klassen in Java, aber Sie können Ihre eigenen erstellen oder eine der funktionalen Bibliotheken für Java verwenden.
Ich empfehle auch, die Eingabedaten einer Funktion zu validieren, bevor Sie die Methode aufrufen, die sie verarbeitet. Wenn Sie diese Aufgaben voneinander trennen, können Sie den Fehler zurückgeben, bevor die Methode aufgerufen wird. Wenn Sie externe Aufrufe erhalten, stellen Sie sicher, dass diese Ihren Code nicht zerstören können, wenn ein Bösewicht etwas Hässliches sendet.
Wenn ein unerwarteter Fehler auftritt, lassen Sie die Anwendung abstürzen. Versuchen Sie nicht, mögliche Ausnahmen mit generischen try Blöcken mit Throwable oder Exception in den catch zu verstecken. Unerwartete Ausnahmen sollten "außergewöhnlich" sein, nicht etwas, das versteckt wird, aber immer wieder vorkommt.
An diesem Punkt müssen Sie möglicherweise einige Dinge rückgängig machen, um Ihr System wieder in einen gesunden Zustand zu versetzen.
Und wenn Ihre Fehlerbehandlung verrückt spielt, sollten Sie erwägen, die Komplexität Ihres Codes durch die Verwendung von Ereignissen zur Fehlerbehandlung zu reduzieren.
Module
In Java 9 wurde ein neues Modulsystem eingeführt, aber in unserem Microservices-Universum besteht die Gefahr, dass wir das Konzept der Module verlieren.
Microservices sind sehr nützlich, können aber schwer zu koordinieren sein und führen zu unnötiger Komplexität. Wenn es nicht unbedingt erforderlich ist, sollten Sie also nicht Unmengen von Microservices erstellen. Erstellen Sie stattdessen kleine, unabhängige, gut getestete Module mit guter Fehlerbehandlung, die einfach zu verwenden und zu entfernen sind und keine Framework-Abhängigkeiten aufweisen. Wenn Sie später einen Microservice bereitstellen müssen, erstellen Sie einfach eine Schnittstelle für Ihr Modul.
Auf diese Weise vermeiden Sie die Wiederholung von Code in Ihren Diensten, Ihre Kollegen verwenden Ihren Code, und Sie können Ihr System leichter testen.
Es ist gut, einige der Klassen zu verstecken, um die Kapselung zu verbessern. Kapselung ist gut für Sie, aber nicht so gut für die Benutzer Ihres Codes, da sie diese Klassen nicht wiederverwenden oder überschreiben können.
Java 9-Module sind eine optionale Technologie, die Sie zu Ihrem Stack hinzufügen können. Wenn Sie sie nicht verwenden möchten, können Sie auch normale Jars verwenden.
Java mag alt sein, aber ...
Vielleicht ist Java nicht cool, weil es alt ist, aber Java ist kompatibel und verfügt über viele Tools, eine große Gemeinschaft und ein erstaunliches Ökosystem. Seine Entwicklung ist zwar etwas langsam, aber es entwickelt sich definitiv weiter: Java ist funktional und containerfähig, es reitet auf der Microservices-Welle, es gibt alle sechs Monate neue Versionen, und mit dem Projekt Loom werden viele erstaunliche Funktionen in das JDK aufgenommen.
Verstehen Sie mich nicht falsch. Ich mag auch andere Sprachen, aber ich ziehe es vor, meine Entwicklungsfähigkeiten in modernem Java weiterzuentwickeln. Ich versuche auch, meine Kollegen in neue Java-Entwicklungsmuster einzuführen, die unsere Arbeit enorm verbessern werden.
Wenn Sie also das Gefühl haben, eine veraltete Sprache zu verwenden, und über einen Wechsel nachdenken, sollten Sie sich überlegen, wie Sie Ihren Code ändern und die neuen Ideen und Muster, die Java bietet, aufgreifen, anstatt in einer anderen Sprache zu schreiben.