Die Migration von Code (sprich: Legacy-Code) ist kein Vergnügen. Es erfordert eine enorme Menge an Planung und Aufwand, um ihn über die Linie zu bringen. Es ist zwar nicht die aufregendste oder motivierendste Arbeit für Entwickler, aber es erfordert Entschlossenheit und die richtige Erfahrung, um Legacy-Code auf neue Bibliotheksversionen zu migrieren. Joda-Time zu java.time ist eine solche Migration, die eine sorgfältige Planung und Ausführung erfordert.
Wenn Ihr Java-Projekt vor Java SE 8 begonnen hat und Datums- und Zeitverarbeitung verwendet, dann hat es wahrscheinlich Joda-Time verwendet - eine ausgezeichnete Bibliothek und ein De-facto-Standard für die Handhabung von Datums- und Zeitfunktionen vor SE 8. Wenn Ihr Projekt immer noch Joda-Time verwendet, aber auf java.time migrieren möchte, dann lesen Sie weiter.
Mit der Veröffentlichung von Java SE 8 wurde eine neue und verbesserte Standard-API für Datum und Uhrzeit eingeführt, die gemeinhin als java.time (JSR-310) bezeichnet wird. Das Joda-Time-Projekt empfiehlt nun die Migration auf java.time (JSR-310).
Obwohl java.time (JSR-310) stark von Joda-Time inspiriert wurde, ist es nicht rückwärtskompatibel und Konzepte und Terminologien haben sich geändert. Deshalb erfordert die Migration von Joda-Time zu java.time eine sorgfältige Prüfung jeder einzelnen Codezeile, die Sie ändern. Dies kann zeitaufwändig sein und man würde sich fast wünschen, dass es einen einfacheren und automatisierten Weg zur Migration gäbe.
Es gibt einen besseren Weg zu migrieren, und wir haben ihn mit Sensei entwickelt - ein IntelliJ-Plugin, das automatisch Code-Transformationen nach den von Ihnen definierten Rezepten (Regeln) durchführt. Verbringen Sie Ihre Zeit damit, wiederverwendbare Rezepte zu definieren, anstatt sich wiederholende Migrationsaufgaben durchzuführen. Die Automatisierung wird nicht nur Ihren alten Joda-Time-Code transformieren, sondern auch den Teams helfen, die Richtlinien direkt in der IDE zu befolgen, wenn sie neuen Code schreiben.
Um Ihnen einen Vorsprung zu verschaffen, haben wir ein öffentliches Sensei Kochbuch Standardization on java.time (JSR-310) erstellt, das Rezepte für die Migration von Joda-Time zu java.time auf eine weniger schmerzhafte Weise enthält. Dies ist ein wachsender Satz von Rezepten, den wir weiter ausbauen werden, um die Abdeckung mit weiteren Rezepten zu erhöhen.
Hier ist ein Beispiel für eine Migration, das Ihnen zeigt, wie Sensei die Migration von Legacy-Code vereinfacht.
Von der sich wiederholenden manuellen Migration zu automatisierten Codeumwandlungen Schauen wir uns ein Beispiel für die Erstellung einer neuen DateTime an, das ein paar versteckte Fallen bei der Migration einer einzigen Codezeile von Joda-Time zu java.time aufzeigt. Wir werden uns dann eines unserer Sensei Rezepte aus unserem Standardization on java.time (JSR-310) Kochbuch ansehen und zeigen, wie es all diese Informationen erfasst, so dass dieselbe Migration von jedem Entwickler wieder und wieder verwendet werden kann.
In diesem Beispiel konstruieren wir eine Joda-Time DateTime aus 7 int-Argumenten, die Werte der DateTime-Felder darstellen.
Wie können wir dies in ein java.time-Äquivalent migrieren?
Die Javadoc in Joda-Time für diesen Konstruktor sagt:
Konstruiert eine Instanz aus Datetime-Feldwerten unter Verwendung von ISOChronology in der Standardzeitzone. Zunächst könnte man annehmen, dass es eine DateTime-Klasse in java.time gibt, aber das ist nicht der Fall. Wenn Sie nach "Migration von Joda-Time zu Java-Time" googeln, werden Sie höchstwahrscheinlich Stephen Colebournes Blogbeitrag Converting from Joda-Time to java.time finden.
Dies ist ein guter Anfang und weist uns den Weg zur Verwendung von java.time.ZonedDateTime oder java.time.OffsetDateTime. Hier stellt sich die erste Frage: Welche soll ich verwenden? Wahrscheinlich ZonedDateTime, basierend auf Stephens Kommentaren.
Wenn wir im Javadoc von ZonedDateTime nachsehen, können wir überhaupt keine Konstruktoren finden. Wir kehren zu Stephens Blogbeitrag zurück und lesen weiter unten:
Konstruktion . Joda-Time hat einen Konstruktor, der ein Objekt akzeptiert und eine Typkonvertierung durchführt. java.time hat nur Factory-Methoden, so dass die Konvertierung ein Benutzerproblem ist, obwohl eine parse()-Methode für Strings zur Verfügung steht.Es muss also eine statische Fabrikmethode geben, und beim Durchsuchen der statischen Methoden finden wir eine, die ziemlich ähnlich aussieht, aber nicht genau dieselbe ist.
Er hat 7 int-Parameter wie unser ursprünglicher Joda-Time DateTime-Konstruktor, aber wenn Sie nicht aufpassen, werden Sie ein wichtiges Detail übersehen. Der 7. Parameter steht nicht mehr für Millisekunden, sondern für Nanosekunden. Das liegt daran, dass java.time die Genauigkeit gegenüber Joda-Time erhöht hat und Instanten auf die Nanosekunde genau misst. Ein wichtiges Detail, das Sie leicht hätten übersehen können. Außerdem erwartet diese Methode eine ZoneId, so dass man sich fragt, warum man vorher keine brauchte und warum man sie jetzt braucht.
Erinnern Sie sich an die Javadoc unserer ursprünglichen Konstruktor, die es würde die Standard-Zeitzone verwenden erwähnt, vielleicht gibt es eine Möglichkeit, die Standard-ZoneId zu erhalten?
In der Javadoc für ZoneId sind keine Konstruktoren aufgeführt, aber wenn wir uns die statischen Methoden ansehen, sehen wir, dass wir systemDefault() verwenden können
Nun, da wir die ZoneId geklärt haben, was sollen wir mit der Konvertierung von Millisekunden in NanoSekunden tun? Vielleicht können wir java.util.concurrent.TimeUnit verwenden, um die Umwandlung durchzuführen.
Diese Methode gibt einen long zurück, und unsere Methode erwartet einen int, so dass wir jetzt auch ein Konvertierungsproblem zu lösen haben. Vielleicht können wir etwas Einfaches versuchen. Eine Multiplikation?
Das funktioniert zwar, sieht aber ein wenig deplatziert aus. Falls Sie es noch nicht bemerkt haben: Wir haben viel Zeit und Mühe investiert, um eine einzige Codezeile zu migrieren. Aber wie Sie sich vorstellen können, müssen wir viele solcher Bearbeitungen von Hand vornehmen, und es wird nicht besser.
Wenn wir uns jedoch die java.time API etwas genauer ansehen, können wir eine Lösung entdecken, die etwas flüssiger aussieht.
Obwohl ZonedDateTime keine offensichtliche Möglichkeit bietet, die Millisekunden zu setzen, kann dies mit der Methode with(TemporalField field, long newValue) erfolgen, wobei ChronoField.MILLI_OF_SECOND als TemporalField verwendet wird.
Und in der Java-Doku wird erwähnt, dass es die Konvertierung in Nanosekunden für uns durchführt:
Wenn dieses Feld zur Einstellung eines Wertes verwendet wird, sollte es sich genauso verhalten wie die Einstellung von NANO_OF_SECOND, wobei der Wert mit 1.000.000 multipliziert wird. Wir können also einfach 0 für unsere Nanosekunden in der Factory-Methode angeben und dann die With-Methode verwenden, um eine ZonedDateTime zu erstellen, die alle ursprünglichen Werte sowie die Millisekunden enthält.
Wenn man sich unser Endergebnis ansieht, sieht es so aus, als hätten wir nur eine einzige Codezeile geändert. Das zeigt nicht wirklich den Aufwand, der mit der Erforschung einer einzigen Migration verbunden war!
Erstellen Sie ein Rezept, um schneller und einfacher zu migrieren Sensei bietet uns die Möglichkeit, diese hart erarbeiteten Informationen mit anderen Entwicklern zu teilen. Durch die Erstellung eines Rezepts, in dem all diese Anforderungen erfasst sind, können die Benutzer von Sensei diese Migration mit einem Mausklick durchführen.
Ein Sensei Rezept besteht aus 3 Hauptteilen:
Metadaten Suche AvailableFixes Schauen wir uns ein Sensei Rezept an (kann auch als YAML-Rezept betrachtet werden), das uns hilft, diesen Aufruf in sein java.time-Äquivalent zu migrieren.
DateTime foo = new DateTime(year, monthOfYear, dayOfMonth, hourOfDay, minuteOfHour, secondOfMinute, millisOfSecond); Abschnitt Metadaten Der Abschnitt Metadaten enthält Informationen über das Rezept und seine Verwendung.
Bereich Suche Der Suchabschnitt eines Sensei Rezepts gibt an, auf welche Code-Elemente dieses Rezept angewendet werden soll.
search: instanceCreation: args: 1: type: int 2: type: int 3: type: int 4: type: int 5: type: int 6: type: int 7: type: int argCount: 7 type: org.joda.time.DateTime
In diesem Suchabschnitt sehen wir, dass wir es sind:
Suche nach einer instanceCreation, d.h. einer Verwendung eines Constructors. Hinweis: Es sind viele andere Suchziele verfügbar Der Konstruktor sollte 7 Argumente haben, dies wird durch die Eigenschaft argCount angegeben args 1-7 sollten vom Typ int seinWir suchen nach Konstruktoren vom Typ org.joda.time.DateTime
Abschnitt Verfügbare Korrekturen Im Abschnitt availableFixes können eine oder mehrere Korrekturen angegeben werden, die auf das entsprechende Codeelement angewendet werden können. Jede Korrektur kann mehrere Aktionen haben, und in unserem Fall haben wir eine einzige Korrektur, die 2 Aktionen ausführt.
Der Name des Fixes wird dem Benutzer im Menü "Quickfixes" angezeigt und beschreibt, was passiert, wenn der Benutzer diesen Quickfix anwendet Die Liste der Aktionen zeigt, welche Aktionen durch diesen Quickfix ausgeführt werden Die Rewrite-Aktion schreibt das Code-Element unter Verwendung einer Mustache-Vorlage um. Dabei können Variablen und Funktionen zur Ersetzung von Zeichenketten verwendet werden. Die Aktion modifyAssignedVariable prüft, ob dieser Konstruktor verwendet wird, um einer Variablen einen Wert zuzuweisen. Wenn dies der Fall ist, ändert diese Aktion die Variable so, dass sie als der Typ deklariert wird, der durch type
Verwendung des Rezepts für die Codeumwandlung Wenn unser Rezept geschrieben und aktiviert ist, scannt es unseren Code und markiert die Segmente, auf die es angewendet werden kann.
Im folgenden Screenshot sehen wir, dass der Zielkonstruktor mit Sensei markiert wurde. Wenn wir den Mauszeiger über den markierten Konstruktor bewegen, sehen wir die Recipe shortDescription und die Quickfix-Option Migrate to java.time.ZonedDateTime
Nachdem wir den Quickfix Migrate to java.time.ZonedDateTime ausgewählt haben, wird der Code entsprechend den im Rezept angegebenen Aktionen umgewandelt.
Eine einmalige Migration und einheitliche Kodierungspraktiken in allen Teams - mit Sensei Wie wir an unserem obigen Beispiel sehen, kann die Migration einer einzigen Codezeile hart erarbeitetes Wissen beinhalten. Sensei kann dieses Wissen in umsetzbare Rezepte oder Kochbücher umwandeln, die innerhalb von Teams geteilt werden können. Sie können einen einmaligen Migrations-Sprint planen oder den Ansatz verfolgen, inkrementelle, sofortige Transformationen zu java.time durchzuführen, sobald Sie auf Joda-Time-Code stoßen. Sie können Rezepte aktivieren/deaktivieren, um Migrationen in logischen Phasen oder Schritten durchzuführen und sogar den Umfang der von Sensei gescannten Dateien zu erweitern oder zu reduzieren - die Flexibilität, die Code-Migrationen weniger schmerzhaft macht.
Die Migration von Bibliotheken ist nur ein Beispiel für die vielen Möglichkeiten, die Sensei bietet, um Ihre Projekte zu standardisieren. Sie können immer nach Anti-Patterns oder bestimmten manuellen Code-Transformationen Ausschau halten, auf die Sie häufig in Pull-Requests oder beim Coding selbst stoßen. Wenn Sie über eine Reihe von Codierungsrichtlinien verfügen, die von den Entwicklern häufig übersehen werden, können Sie diese Richtlinien in Rezepte umwandeln, so dass die Entwickler bewährte Codeumwandlungen sicher anwenden können.
Wenn Sie Fragen haben, würden wir uns freuen, von Ihnen zu hören! Treten Sie uns auf Slack bei: sensei -scw.slack.com