7 conseils pour faire briller votre bibliothèque Kotlin

Préface

Lorsque nous commençons à programmer dans un nouveau langage, au début, nous nous en tenons souvent aux paradigmes et aux habitudes développés lors de la programmation dans le langage que nous connaissons déjà. Bien que cela puisse sembler acceptable au début, cette rugosité est assez évidente pour les personnes qui programment dans la langue que vous essayez de maîtriser pendant un certain temps.

Quand je venais de passer de Java à Kotlin il y a environ 2 ans, je faisais essentiellement de la «programmation Java mais en Kotlin». Même si Kotlin était comme une bouffée d’air frais pour moi, en tant qu’ingénieur Android, j’utilisais toutes ces fonctions d’extension et ces classes de données qui n’étaient pas tout à fait cohérentes. J'étais en train de convertir du code Java existant en Kotlin et, après un certain temps, il m'a permis de garder quelques idées en tête lorsque j'ai conçu des API pour différents composants ou bibliothèques réutilisables dans Kotlin.

Ici, chez BUX, nous travaillons sur deux applications qui partagent deux bibliothèques partagées. Alors que l'application Stocks (qui n'est pas encore publiée) était écrite en Kotlin depuis le début, la première application CFD a été écrite en Java, puis convertie en Kotlin après un certain temps. Actuellement, l’application CFD utilise 78,7% de Kotlin, ce qui signifie que nous y travaillons toujours. C’est pourquoi il est important de prêter attention aux API de bibliothèque gérées par deux équipes utilisant simultanément Java et Kotlin.

Donc, si vous avez une bibliothèque Kotlin ou Java existante que vous souhaitez Kotlinify ou que vous concevez une API utilisant Kotlin à partir de zéro, demandez-moi quelques idées sur ce que vous pouvez faire pour rendre la vie de vos utilisateurs de bibliothèque plus facile.

1. Fonctions d'extension

La plupart du temps, lorsque vous avez besoin d'étendre une classe existante avec une nouvelle fonctionnalité, vous devez utiliser une sorte de composition ou en dériver une nouvelle classe (qui, si elle est utilisée de manière extensive, peut rendre votre hiérarchie de classe aussi fragile que les ventouses d'IKEA). Quelle que soit votre préférence, Kotlin a sa propre réponse, appelée fonctions d'extension. En un mot, il vous permet d’ajouter une nouvelle fonction à une classe existante avec une syntaxe très soignée. Sous Android, par exemple, vous pouvez définir une nouvelle méthode pour la classe View de la manière suivante:

Gardant cela à l'esprit, de nombreuses bibliothèques fournissant des fonctionnalités supplémentaires pour les classes existantes (par exemple, View, Context, etc.), au lieu d'utiliser des décorateurs, les méthodes de fabrique statique ou quelque chose de différent peuvent tirer avantage de la fourniture transparente de leurs fonctionnalités en tant qu'Extension, la fonctionnalité existerait dans les classes d'origine depuis le début.

2. Valeurs d'argument par défaut

Kotlin a été conçu à l'origine pour être une version concise, nette et ordonnée de Java. Il s'avère que cela fonctionne très bien avec les valeurs d'argument de fonction par défaut. Le fournisseur de l'API peut spécifier des valeurs par défaut pour les arguments pouvant être omis. Je serais vraiment surpris si vous n’avez pas vu un code similaire lorsque vous utilisez SQLite dans Android:

Même s'il y a plus de défauts dans cette méthode que ces laids nuls à la fin, je ne suis pas ici pour juger, mais plutôt pour dire que ces temps sont révolus et que je remettrais sérieusement en cause le code écrit de cette façon dans Kotlin. Il existe de nombreux exemples comme celui-ci, mais heureusement, nous pouvons faire mieux maintenant et si vous fournissez une API externe avec certains paramètres de réglage, au lieu ou en plus de fournir un générateur, envisagez d'utiliser des valeurs d'argument par défaut. Cela aura au moins deux avantages: tout d’abord, vous ne forcerez pas un utilisateur à spécifier des paramètres facultatifs ou tout ce que vous pouvez prédire à l’avance, et le second est que l’utilisateur aura une configuration par défaut qui fonctionnera simplement de manière inattendue. -boîte:

Il est également intéressant de mentionner ici que si vous définissez ces arguments avec des valeurs par défaut, un utilisateur ne devra pas spécifier de nom pour les paramètres obligatoires.

3. objets

Avez-vous déjà eu à implémenter vous-même le modèle Singleton en Java? Vous en aviez probablement et si vous deviez savoir à quel point cela pouvait être fastidieux:

Il existe différentes implémentations, chacune avec leurs propres avantages et inconvénients. Kotlin traite ces 50 nuances de mise en œuvre du modèle Singleton avec une seule structure appelée déclaration d'objet. Look ma, pas de “double contrôle de verrouillage”:

Ce qui est bien, à part la syntaxe, c’est que l’initialisation de la déclaration d’objet est à la fois thread-safe et initialisée paresseusement lors de son premier accès:

Pour des bibliothèques telles que Fabric, Glide, Picasso ou toute autre qui utilise une seule instance de l'objet comme point d'entrée principal de l'API de bibliothèque globale, il s'agit d'un moyen naturel d'aller maintenant et il n'y a aucune raison d'utiliser l'ancien moyen Java pour faire les mêmes choses.

4. Fichiers source

De la même manière, Kotlin repense de nombreux problèmes syntaxiques auxquels nous sommes confrontés au quotidien, mais également la manière dont nous organisons le code que nous créons. Les fichiers source Kotlin servent de base à plusieurs déclarations sémantiquement proches les unes des autres. Il s’avère qu’ils sont l’endroit idéal pour définir certaines fonctions d’extension liées à la même classe. Découvrez cet extrait simplifié du code source de Kotlin, où toutes les fonctions d'extension utilisées pour manipuler du texte sont situées dans le même fichier «Strings.kt»:

Un autre exemple d'utilisation est la définition d'un protocole de communication avec votre API, ainsi que les classes de données et les interfaces dans un fichier source unique. Cela permettra à un utilisateur de ne pas perdre le focus en basculant entre différents fichiers tout en suivant un flux:

Mais n’allez pas trop loin avec cela car vous risqueriez de surcharger un utilisateur avec la taille du fichier et de le transformer en classe RecyclerView avec environ 13 000 lignes.

5. Coroutines

Si votre bibliothèque utilise plusieurs threads pour accéder à un réseau ou effectuer tout autre travail de longue durée, envisagez de fournir une API avec des fonctions de suspension. Depuis Kotlin 1.3, les coroutines ne sont plus des expériences, c’est une excellente occasion de les utiliser en production si vous avez déjà eu des doutes. Dans certains cas, les coroutines peuvent constituer une bonne alternative àObservable from RxJava et à d'autres moyens de gérer les appels asynchrones. Ce que vous avez peut-être déjà vu auparavant est l'API regorgeant de méthodes avec des rappels de résultats ou au moment du battage publicitaire Rx enveloppé avec Single ou Completable:

Mais nous sommes maintenant dans l'ère de Kotlin, de sorte que cela pourrait également être conçu en faveur de l'utilisation de coroutines:

Bien que les coroutines fonctionnent mieux que les bons vieux threads Java en termes de légèreté, elles contribuent de manière significative à la lisibilité du code:

6. Contrats

Outre les coroutines stables, Kotlin 1.3 présente également aux développeurs la manière d’interagir avec un compilateur. Un contrat est une nouvelle fonctionnalité qui nous permet, en tant que développeurs de bibliothèques, de partager avec un compilateur les connaissances dont nous disposons en spécifiant ce que l'on appelle des effets. Pour faciliter la compréhension de l’effet, apportons l’analogie la plus proche de Java. La plupart d’entre vous ont probablement déjà vu ou utilisé la classe Precondition de Guava avec beaucoup d’affirmations et son adaptation dans des bibliothèques comme Dagger, qui ne veulent pas aller chercher une bibliothèque entière:

Si l'objet de référence transmis est null, la méthode lève une exception et le code restant placé après cette méthode ne sera pas atteint. Nous pouvons donc supposer que la référence ne sera pas nulle. Voici le problème - même si nous avons cette connaissance, cela n’aide pas le compilateur à faire la même supposition. C’est là que les annotations @ Nullable et @ NotNull entrent en scène telles qu’elles existent uniquement pour aider le compilateur à comprendre les conditions. C’est ce qu’est un effet - c’est un indice pour un compilateur qui l’aide à effectuer une analyse de code plus sophistiquée. Un effet existant que vous avez déjà vu dans Kotlin est la conversion intelligente d'un certain type dans le bloc après vérification de son type:

Le compilateur Kotlin est déjà intelligent et il ne fait aucun doute qu’il continuera à s’améliorer, mais avec l’introduction des contrats, les développeurs de Kotlin nous ont donné le pouvoir de les améliorer en écrivant nos propres smart-cast et en créant des effets qui aident nos utilisateurs à écrire un code plus propre. Maintenant, réécrivons la méthode checkNotNul dans Kotlin en utilisant Contracts pour démontrer ses capacités:

Il y a également différents effets, mais il ne s’agit pas d’une introduction complète aux contrats et j’espère que cela vous a donné une idée de la façon dont vous pouvez en tirer parti. En plus de cela, le dépôt stdlib sur Github contient de nombreux exemples de contrats utiles qui méritent d’être vérifiés.

Un grand pouvoir entraîne de grandes responsabilités et les contrats ne font pas exception. Tout ce que vous déclarez dans votre contrat est traité par le compilateur comme une Sainte Bible. Plus techniquement, le compilateur ne remet pas en question ni ne valide tout ce que vous écrivez. Vous devez donc inspecter soigneusement votre code et assurez-vous de ne pas introduire d’incohérences.

Comme vous l'avez peut-être remarqué dans l'annotation @ExperimentalContracts, les contrats étant encore en phase expérimentale, l'API peut non seulement changer au fil du temps, mais certaines nouvelles fonctionnalités peuvent en découler à mesure de sa maturité croissante.

7. Interopérabilité Java

Lorsque vous écrivez une bibliothèque dans Kotlin, il est également important de garder le public plus large en offrant une expérience d’intégration harmonieuse à vos collègues développeurs qui utilisent Java. C’est très important car il ya encore beaucoup de projets qui s’en tiennent à Java et qui ne veulent pas réécrire le code existant pour diverses raisons. Puisque nous voulons que la communauté Kotlin se développe plus rapidement, il est préférable de permettre à ces projets de le faire progressivement, étape par étape. Bien sûr, il n’ya pas beaucoup de soleil de ce côté-ci du mur, mais vous pouvez, en tant que responsable de la bibliothèque, faire certaines choses à ce sujet.

La première chose à faire est que les fonctions d'extension Kotlin en Java seront compilées en méthodes statiques laides, où le premier argument de la méthode est un type de destinataire:

Bien que vous n’ayez pas vraiment beaucoup de contrôle ici, vous pouvez au moins changer le nom de la classe générée en ajoutant la ligne suivante au début du fichier source contenant les fonctions au niveau du package ci-dessus:

Un autre exemple de l'impact que vous pouvez avoir sur un code généré est lié à l'utilisation d'une méthode d'objet compagnon:

Vous pouvez forcer Kotlin à générer des classes statiques à la place des fonctions définies dans l'objet compagnon à l'aide de l'annotation @JvmStatic:

En comparant à Kotlin, deux fonctions Java portant des noms similaires mais différents types génériques ne peuvent pas être définies ensemble en raison de l’effacement des types, ce qui pourrait constituer un obstacle pour les utilisateurs Java. Espérons que vous pourrez l'éviter en modifiant la signature de la méthode avec une autre annotation:

Enfin, vous avez la possibilité de définir des exceptions vérifiées dans les fonctions destinées aux utilisateurs de Java, bien qu’elles ne soient pas directement disponibles dans Kotlin. Cela pourrait être pratique car le paradigme de la déclaration des exceptions en Java et en Kotlin est différent:

La plupart des éléments ici sont facultatifs, mais ils pourraient certainement constituer une source de félicitations de la part de vos utilisateurs Java pour votre bibliothèque.

Ligne de fond

Kotlin est un excellent langage qui évolue constamment et vous pouvez faire beaucoup de choses avec la bibliothèque pour en faciliter l'utilisation. Parmi tout ce que vous appliquez à partir des idées ci-dessus, essayez d’être d’abord un utilisateur et réfléchissez à ce que vous aimeriez qu’il soit et à son utilisation. Le diable est dans les détails et, espérons-le, ces petites choses que vous appliquerez profiteront à vos utilisateurs et amélioreront leur expérience. À votre santé!