7 Tipps, wie Sie Ihre Kotlin-Bibliothek zum Leuchten bringen

Vorwort

Wenn wir anfangen, in einer neuen Sprache zu programmieren, halten wir uns zunächst oft an die Paradigmen und Gewohnheiten, die während des Programmierens in der Sprache entwickelt wurden, die wir bereits kennen. Obwohl es anfangs vielleicht in Ordnung zu sein scheint, ist diese Rauheit für Leute, die in der Sprache programmieren, die Sie für eine Weile zu beherrschen versuchen, ziemlich offensichtlich.

Als ich vor ungefähr 2 Jahren von Java zu Kotlin gewechselt war, habe ich im Grunde genommen Java-Programmierung gemacht, aber in Kotlin. Obwohl Kotlin für mich wie ein Hauch frischer Luft war, habe ich als Android-Ingenieur all diese tollen Erweiterungsfunktionen und Datenklassen verwendet und war nicht immer konsistent. Ich habe vorhandenen Java-Code in Kotlin konvertiert und mir nach einiger Zeit einige Gedanken gemacht, als ich APIs für verschiedene wiederverwendbare Komponenten oder Bibliotheken in Kotlin entwarf.

Hier bei BUX arbeiten wir an zwei Anwendungen mit mehreren gemeinsam genutzten Bibliotheken. Während die Aktienanwendung (die noch nicht veröffentlicht wurde) von Anfang an in Kotlin geschrieben wurde, wurde die erste CFD-Anwendung in Java geschrieben und nach einer Weile in Kotlin konvertiert. Momentan ist die CFD-Anwendung 78,7% Kotlin, was bedeutet, dass wir noch daran arbeiten. Aus diesem Grund ist es wichtig, auf die Bibliotheks-APIs zu achten, die von zwei Teams verwaltet werden, die gleichzeitig Java und Kotlin verwenden.

Wenn Sie also eine vorhandene Kotlin- oder Java-Bibliothek haben, die Sie kotlinifizieren möchten, oder wenn Sie eine API mit Kotlin von Grund auf neu entwerfen, sollten Sie sich mit mir überlegen, wie Sie das Leben Ihrer Bibliotheksbenutzer erleichtern können.

1. Erweiterungsfunktionen

In den meisten Fällen, in denen Sie eine vorhandene Klasse mit einer neuen Funktionalität erweitern müssen, verwenden Sie entweder eine Art Komposition oder leiten eine neue Klasse ab (die bei intensiver Verwendung Ihre Klassenhierarchie so zerbrechlich machen kann wie Glasbecher von IKEA). Ganz gleich, was Sie bevorzugen, Kotlin hat eine eigene Antwort darauf, die so genannten Erweiterungsfunktionen. Kurz gesagt, können Sie einer vorhandenen Klasse eine neue Funktion mit einer recht übersichtlichen Syntax hinzufügen. In Android können Sie beispielsweise eine neue Methode für die View-Klasse folgendermaßen definieren:

In Anbetracht dessen können viele Bibliotheken, die zusätzliche Funktionen für die vorhandenen Klassen (z. B. View, Context usw.) bereitstellen, anstatt Dekoratoren, statische Factory-Methoden oder etwas anderes zu verwenden, von der nahtlosen Bereitstellung ihrer Funktionen als Erweiterungsfunktionen profitieren Funktionalität würde in den ursprünglichen Klassen von Anfang an vorhanden sein.

2. Standardargumentwerte

Kotlin wurde ursprünglich als übersichtliche, ordentliche Version von Java entwickelt, und es hat sich herausgestellt, dass dies mit den Standardwerten für Funktionsargumente sehr gut funktioniert. Der Anbieter der API kann Standardwerte für Argumente angeben, die weggelassen werden können. Ich wäre sehr überrascht, wenn Sie bei der Arbeit mit SQLite unter Android keinen ähnlichen Code gesehen hätten:

Auch wenn diese Methode mehr Mängel aufweist als diese hässlichen Nullen am Ende, möchte ich hier nicht beurteilen, sondern nur sagen, dass diese Zeiten vorbei sind und ich würde den in Kotlin so geschriebenen Code ernsthaft in Frage stellen. Es gibt viele Beispiele wie dieses, aber zum Glück können wir jetzt bessere Ergebnisse erzielen. Wenn Sie eine externe API mit bestimmten Optimierungsparametern bereitstellen, können Sie anstelle eines Builders oder zusätzlich dazu auch Standardargumentwerte verwenden. Dies hat mindestens zwei Vorteile: Erstens müssen Sie einen Benutzer nicht zwingen, optionale Parameter anzugeben, oder was auch immer Sie im Voraus vorhersagen können, und zweitens muss der Benutzer eine Standardkonfiguration haben, die einfach nicht funktioniert -Box:

Wenn Sie diese Argumente am Ende mit Standardwerten versehen, muss der Benutzer keine Namen für obligatorische Parameter angeben.

3. Gegenstände

Mussten Sie das Singleton-Muster jemals selbst in Java implementieren? Sie hatten wahrscheinlich und wenn ja, sollten Sie wissen, wie umständlich es manchmal sein könnte:

Es gibt verschiedene Implementierungen mit jeweils eigenen Vor- und Nachteilen. Kotlin adressiert alle diese 50 Schattierungen der Singleton-Musterimplementierung mit einer einzigen Struktur namens Objektdeklaration. Schau ma, keine "doppelt geprüfte Verriegelung":

Abgesehen von der Syntax ist es cool, dass die Initialisierung der Objektdeklaration sowohl thread-sicher ist als auch beim ersten Zugriff verzögert initialisiert wird:

Für Bibliotheken wie Fabric, Glide, Picasso oder andere, die eine einzelne Instanz des Objekts als Haupteinstiegspunkt für die gesamte Bibliotheks-API verwenden, ist dies ein natürlicher Weg und es gibt keinen Grund, den alten Java-Weg zu verwenden mach die gleichen Dinge.

4. Quelldateien

So wie Kotlin viele syntaktische Herausforderungen, mit denen wir täglich konfrontiert sind, überdenkt, überdenkt er auch die Art und Weise, wie wir den von uns erstellten Code organisieren. Kotlin-Quelldateien dienen als Basis für mehrere Deklarationen, die semantisch nahe beieinander liegen. Es stellt sich heraus, dass sie der perfekte Ort sind, um einige Erweiterungsfunktionen zu definieren, die sich auf dieselbe Klasse beziehen. Schauen Sie sich dieses vereinfachte Snippet aus dem Kotlin-Quellcode an, in dem sich alle Erweiterungsfunktionen zum Bearbeiten von Text in derselben Datei "Strings.kt" befinden:

Ein weiteres geeignetes Beispiel für die Verwendung ist die Definition eines Kommunikationsprotokolls mit Ihrer API zusammen mit den Datenklassen und Schnittstellen in einer einzelnen Quelldatei. Dadurch kann ein Benutzer den Fokus nicht verlieren, indem er zwischen verschiedenen Dateien wechselt, während er einem Ablauf folgt:

Aber gehen Sie nicht zu weit, da Sie das Risiko eingehen, einen Benutzer mit der Dateigröße zu überfordern und ihn in eine RecyclerView-Klasse mit ~ 13.000 Zeilen zu verwandeln.

5. Koroutinen

Wenn Ihre Bibliothek mehrere Threads verwendet, um auf ein Netzwerk zuzugreifen oder eine andere lang andauernde Arbeit auszuführen, sollten Sie in Erwägung ziehen, eine API mit Suspend-Funktionen bereitzustellen. Ab Kotlin 1.3 sind Coroutinen nicht mehr experimentell, was eine gute Gelegenheit ist, sie in der Produktion einzusetzen, wenn Sie zuvor Zweifel hatten. In einigen Fällen sind Coroutines eine gute Alternative zu Observable von RxJava und anderen Möglichkeiten, mit asynchronen Aufrufen umzugehen. Was Sie vielleicht schon vorher gesehen haben, ist die API voller Methoden mit Ergebnisrückrufen oder zum Zeitpunkt des Rx-Hype mit Single oder Completable:

Aber wir sind jetzt in der Ära von Kotlin, also könnte es auch zugunsten der Verwendung von Coroutinen entworfen werden:

Obwohl Coroutines eine bessere Leistung als gute alte Java-Threads aufweisen, tragen sie erheblich zur Lesbarkeit des Codes bei:

6. Verträge

Neben stabilen Coroutinen bietet Kotlin 1.3 Entwicklern auch die Möglichkeit, mit einem Compiler zu interagieren. Ein Vertrag ist eine neue Funktion, mit der wir als Bibliotheksentwickler unser Wissen mit einem Compiler teilen können, indem wir sogenannte Effekte festlegen. Um die Wirkung besser nachvollziehen zu können, bringen wir die nächstliegende Analogie zu Java. Die meisten von Ihnen haben wahrscheinlich die Precondition-Klasse aus Guava mit vielen Behauptungen und ihrer Anpassung in Bibliotheken wie Dagger gesehen oder sogar verwendet, die nicht die gesamte Bibliothek abrufen möchten:

Wenn das übergebene Referenzobjekt null ist, löst die Methode eine Ausnahme aus, und der verbleibende Code, der nach dieser Methode platziert wird, wird nicht erreicht. Daher können wir davon ausgehen, dass die Referenz dort nicht null ist. Hier kommt das Problem - obwohl wir über dieses Wissen verfügen, hilft es dem Compiler nicht, die gleiche Annahme zu treffen. Hier kommen die Annotationen @ Nullable und @ NotNull in die Szene, da sie nur existieren, um dem Compiler das Verständnis der Bedingungen zu erleichtern. Dies ist, was ein Effekt wirklich ist - es ist ein Hinweis für einen Compiler, der ihm hilft, eine ausgefeiltere Code-Analyse durchzuführen. Ein bestehender Effekt, den Sie bereits in Kotlin gesehen haben, ist das Smartcasting eines bestimmten Typs im Block, nachdem dessen Typ überprüft wurde:

Der Kotlin-Compiler ist bereits intelligent und wird sich in Zukunft zweifellos weiter verbessern. Mit der Einführung von Verträgen haben uns die Kotlin-Entwickler als Bibliotheksentwickler die Möglichkeit gegeben, ihn durch das Schreiben eigener Smart-Casts und das Erstellen von Effekten zu verbessern, die unseren Benutzern helfen Schreiben Sie saubereren Code. Nun schreiben wir die checkNotNul-Methode in Kotlin neu und verwenden Verträge, um ihre Fähigkeiten zu demonstrieren:

Es gibt auch verschiedene Effekte, aber dies ist keine vollständige Einführung in Verträge, und ich hoffe, Sie haben nur eine Vorstellung davon, wie Sie davon profitieren können. Darüber hinaus bietet das stdlib-Repository auf Github eine Vielzahl nützlicher Vertragsbeispiele, die es wert sind, überprüft zu werden.

Mit großer Macht geht große Verantwortung einher und Verträge sind keine Ausnahme. Was auch immer Sie in Ihrem Vertrag angeben, wird vom Verfasser als die Heilige Bibel behandelt. Technisch gesehen hinterfragt oder validiert der Compiler nicht alles, was Sie dort schreiben, sodass Sie Ihren Code sorgfältig selbst überprüfen und sicherstellen müssen, dass Sie keine Inkonsistenzen einführen.

Wie Sie vielleicht anhand der @ExperimentalContracts-Annotation bemerkt haben, befinden sich die Verträge noch in der Testphase, sodass sich nicht nur die API im Laufe der Zeit ändern, sondern auch einige neue Funktionen daraus ableiten können, wenn sie immer ausgereifter werden.

7. Java-Interoperabilität

Beim Schreiben einer Bibliothek in Kotlin ist es auch wichtig, das Publikum zu erweitern, indem Sie Ihren Kollegen, die Java verwenden, eine reibungslose Integrationserfahrung bieten. Dies ist sehr wichtig, da es immer noch viele Projekte gibt, die sich an Java halten und aus verschiedenen Gründen den vorhandenen Code nicht umschreiben möchten. Da wir möchten, dass die Kotlin-Community schneller wächst, ist es besser, diese Projekte schrittweise dazu zu bewegen. Natürlich ist es auf dieser Seite der Mauer nicht so sonnig, aber es gibt einige Dinge, die Sie als Bibliotheksverwalter tun können.

Das erste ist, dass Kotlin-Erweiterungsfunktionen in Java in hässliche statische Methoden kompiliert werden, wobei das erste Argument der Methode ein Empfängertyp ist:

Obwohl Sie hier nicht wirklich viel Kontrolle haben, können Sie den Namen der generierten Klasse zumindest ändern, indem Sie die folgende Zeile am Anfang der Quelldatei einfügen, die die obigen Funktionen auf Paketebene enthält:

Ein weiteres Beispiel, wie Sie einen generierten Code geringfügig beeinflussen können, hängt mit der Verwendung von Begleitobjektmethoden zusammen:

Sie können Kotlin zwingen, statische Klassen anstelle von Funktionen zu generieren, die im Begleitobjekt mit der Annotation @JvmStatic definiert wurden:

Im Vergleich zu Kotlin können in Java zwei Funktionen mit ähnlichen Namen, aber unterschiedlichen generischen Typen aufgrund der Typlöschung nicht gemeinsam definiert werden, sodass sie für Java-Benutzer ein Ausrufezeichen sein können. Sie können dies hoffentlich vermeiden, indem Sie die Signatur der Methode durch eine andere Anmerkung ändern:

Zu guter Letzt besteht die Möglichkeit, aktivierte Ausnahmen in Funktionen für Java-Benutzer zu definieren, obwohl diese nicht direkt in Kotlin verfügbar sind. Dies könnte nützlich sein, da das Paradigma der Ausnahmedeklaration in Java und Kotlin unterschiedlich ist:

Die meisten Dinge hier sind optional, aber sie könnten sicherlich eine Quelle des Kudos für Ihre Bibliothek von Ihren Java-Benutzern sein.

Endeffekt

Kotlin ist eine großartige Sprache, die sich ständig weiterentwickelt und es gibt eine Menge Dinge, die Sie tun können, um die Nutzung der Bibliothek reibungslos zu gestalten. Versuchen Sie unter allem, was Sie aus den oben genannten Ideen anwenden, zunächst, ein Benutzer zu sein, und überlegen Sie, was Sie möchten und wie Sie es verwenden möchten. Der Teufel steckt im Detail und hoffentlich werden diese kleinen Dinge, die Sie anwenden, Ihren Benutzern zugute kommen und ihre Erfahrung verbessern. Prost!