Comment puis-je aider mon équipe à aimer RxJS?

Comprendre la cause et l’effet est au cœur de la compréhension de toute application, et la programmation réactive détruit la capacité de la voir clairement. Mais c’est aussi très puissant. Alors, comment pouvons-nous aider nos collègues développeurs à aimer travailler avec cela?

Tout le monde n'aime pas la programmation réactive. De nombreux développeurs préfèrent la programmation impérative, car elle est simple et directe. Si vous cliquez sur un bouton et que les données à 3 endroits sur la page doivent changer, alors… prenons ces 3 endroits et changeons le texte!

Avec de plus en plus de personnes utilisant Redux et RxJS, de nombreuses applications deviennent de plus en plus difficiles à comprendre, à déboguer et à développer. Ces technologies aident à prévenir les bugs liés à un état incohérent, mais le compromis est l’énorme quantité de caractère indirect qu’elles introduisent.

Prenons l'exemple suivant: dans une application de discussion, un développeur peut constater que, lorsqu'il clique sur un bouton «Quitter la discussion», le fil de discussion est supprimé de la liste des discussions de la vue, mais le WebSocket n'est pas fermé correctement. L’architecture réactive empêche maintenant de comprendre pourquoi cela se produit. Lorsque le développeur examine le gestionnaire d'événements pour Leave Chat, il ne voit que this.store.dispatch ({type: 'LEAVE_CHAT', id}). Après avoir suivi le code pendant un moment, ils parviennent enfin à la cause de la disparition de l'élément de discussion dans la liste. Il leur semble que l’identification du mécanisme de cause à effet dans le processus n’aurait pas dû être aussi difficile. Ils s'attendaient à quelque chose dans le gestionnaire d'événements comme ceci.chatService.removeChatFromList ({id}); afin qu'ils puissent également ajouter this.chatService.ohAndCloseThisWebSocketPlz ({id}) ;. Mais ce n’était pas là!

Les défenseurs des programmes réactifs sont-ils sadiques?

Peut être. Mais si vous êtes habitué à la programmation réactive, vous avez peut-être remarqué que le bogue de cet exemple n’existerait pas si le code avait été réellement réactif. La liste de discussion aurait souscrit à la liste centrale des identifiants de discussion, de même que les observables responsables de l’ouverture et de la fermeture des connexions de socket Web, et les deux auraient réagi au bon moment. Le gestionnaire d’actions n’aurait pas eu besoin de se souvenir des deux.

Faire tout impérativement, c'est s'assurer que vous avez bien pris en compte toutes les combinaisons d'effets pouvant résulter de la gestion d'une action. La programmation impérative peut être directe et facile à suivre, mais une fois que l'application atteint un certain niveau de complexité, les développeurs ont très peu de chance d'oublier de petites choses ici et là, et l'état incohérent commence à expliquer la majorité des bugs.

Le code impératif peut également avoir des conséquences inattendues. Une caractéristique obscure peut changer subitement un état dont dépend une autre, ou un état peut être muté dans le mauvais ordre, déchirant un portail en enfer et nous obligeant à collecter des cartes-clés de différentes couleurs pour retrouver un portail.

Cartes de couleurs variées

Mais la plupart des développeurs préfèrent toujours fortement le code direct, impératif. Que font-ils alors quand le mandat vient d’en haut d’utiliser cette architecture indirecte Redux / RxJS?

Le résultat le plus probable est que les développeurs vont commencer à voir l'état et le magasin comme une étape supplémentaire dans le processus de réalisation des choses. Alors ils commencent à créer des arbres d'état comme ceci:

// Ne faites pas cela:
Etat de l'interface: {
  showModal: booléen;
  loadItemDetails: booléen;
  showLoadingIcon: booléen;
  cancelRequest: boolean;
  hidePopup: booléen;
  browseToPage: string;
  cleanRoom: booléen;
  goOutside: booléen;
  faireTout: booléen;
  engageInPhilanthropy: boolean;
}

L’état devient effectivement un véhicule gênant pour les commandes. Mais c’est en réalité supposé être un instantané d’informations à partir duquel une et une seule vue peut être générée. Alors, où cela nous met-il? Pire que ce que nous étions avec une programmation impérative, parce que non seulement nous avons maintenant toutes ces machines ridicules Redux / RxJS partout, mais nous devons toujours nous rappeler les effets de toutes ces commandes.

(Remarque: RxJS peut être utilisé sans Redux bien sûr, mais sans Redux, vous aurez probablement toujours le problème suivant: les commandes sont émises sous forme de valeurs observables si vous ne codez pas de manière réactive.)

C’est très difficile de passer d’un état d’esprit impératif à un état d’esprit réactif, c’est pourquoi il faut vraiment vouloir. De nombreux développeurs ne sont pas intéressés par une pensée réactive, car ils n’ont pas encore beaucoup souffert d’un état incohérent, ou n’ont peut-être pas remarqué qu’un état incohérent constitue l’un des points communs entre de nombreux bugs rencontrés.

Quand choisir la programmation réactive

Comme il est contre-productif de faire pression pour une programmation réactive lorsque la motivation fait défaut, la programmation réactive semble avoir un sens dans ces situations:

  1. La valeur de la programmation réactive dans le projet est suffisamment évidente pour que tous les membres de l’équipe souhaitent l’utiliser, malgré ses inconvénients.
  2. Le projet s'y prête assez bien, l'équipe n'y est pas particulièrement opposée et vous disposez de bons moyens pour garantir des schémas réactifs (par exemple, ne stocker qu'un seul état dans le magasin, utiliser des sélecteurs pour les données dérivées et éviter un état local).
  3. Le coût de la programmation réactive peut en quelque sorte être réduit, de sorte que même les équipes travaillant sur des projets simples voudront l’utiliser.

Comme la programmation réactive peut considérablement simplifier des applications complexes, la troisième situation est idéale à mon avis. Si nous pouvions en quelque sorte réduire le coût de l’architecture réactive, les équipes pourront l’adopter tant que leurs applications sont encore petites, avant de perdre une quantité énorme de ressources à la suite des incendies causés par un état mutable et mutable.

Diminuer le coût réactif

Le codage réactif augmente la difficulté de résoudre un problème la première fois et de comprendre une solution existante. Heureusement, de nombreux membres de la communauté travaillent sur des outils permettant de relever ces défis. Pour le reste de cet article, je voudrais partager quelques-uns des sujets qui me passionnent le plus, ainsi que quelques-unes de mes propres idées sur la manière dont nous pourrions rendre plus facile la compréhension et la création de code réactif.

Comprendre le code réactif

La clé pour comprendre le code réactif consiste à rétablir la transparence des causes et des effets dans l’application. Voici quelques outils qui, à mon avis, font un excellent travail dans ce domaine:

  • Redux-devtools. Les actions sont une source d'indirection dans les applications Redux, qui séparent ce qui s'est passé de la manière dont l'état est censé changer (l'état "réagit" aux actions). Redux-devtools reconnecte ces concepts en montrant comment les actions changent d'état. Voici une belle vidéo montrant ce qu’elle peut faire.
  • Andre Staltz a créé un outil de visualisation soigné pour les observables dans sa bibliothèque JS, CycleJS. Vous pouvez regarder les données circuler dans votre application à la vitesse que vous choisissez. J'aimerais voir quelque chose comme ça pour les applications RxJS.
  • Inspiré par Andre Staltz et d’autres, ce site Web vous permet de visualiser n’importe quelle chaîne observable. Je pense que ce serait génial en tant qu'extension VSCode.

Ce sont des outils formidables, et il existe encore de nombreuses possibilités d’améliorer la transparence du code réactif. Si vous avez des idées, veuillez laisser un commentaire ci-dessous et peut-être que vous-même ou une personne pourrez choisir une idée et l'exécuter.

Résoudre de manière réactive

La difficulté à résoudre un problème de manière réactive est que vous devez garder tant de choses dans votre tête que vous dépassez rapidement votre capacité et que vous devez recommencer, peut-être plusieurs fois avant de finalement le résoudre. C'est comme si quelqu'un vous demandait de résoudre un problème de maths comme celui-ci dans votre tête:

32058
X 17
-----

Se souvenir de tous les chiffres est difficile, mais il faut aussi se souvenir des étapes intermédiaires, puis calculer les interactions entre les chiffres est un peu plus que ce que la plupart des gens sont capables de faire.

C’est donc une bonne chose que de le faire dans votre tête ne soit pas le seul moyen de résoudre ce problème. Il existe une méthode plus simple qui consiste à écrire plusieurs petites étapes. Ce processus éloigne le principal problème des limites de la mémoire de travail à un processus plus fiable pouvant être appris et enseigné.

Existe-t-il un processus similaire pour coder de manière réactive?

Prenons un exemple ordinaire, comparons les solutions impératives et réactives et voyons si nous ne pouvons pas extraire un processus de la solution réactive que nous pourrions utiliser pour nous aider à résoudre des problèmes plus complexes.

Exemple de base: Complétion automatique asynchrone

La plupart des développeurs connaissent assez bien cet exemple. Un utilisateur tape du texte dans une entrée, une liste filtrée de données contenant la chaîne qu'il a saisie est récupérée et les résultats sont montrés à l'utilisateur.

La solution impérative est simple. Nous commençons avec le gestionnaire d'événement (change). Cette fonction prendra le terme de recherche en entrée, le passera dans une fonction de récupération de données, puis, lorsque les données seront renvoyées, il liera les données à la classe de composant à afficher. Voici un exemple d'implémentation de ceci:

Autocomplete implémenté impérativement

Génial. Ce n’était pas trop difficile.

La solution réactive est plus difficile à décrire, mais c’est ce que je pensais: Les données doivent être récupérées après la saisie de l’utilisateur, de sorte que l’observable qui récupère les données surveille les entrées de l’utilisateur. L'entrée utilisateur sera un sujet dont la méthode .next est appelée dans le gestionnaire d'événements (change). Il semble donc que la méthode d'extraction de données devra désactiver MapMap de ce sujet. Mais ce flux doit être affecté en tant que propriété sur le composant afin qu’il soit disponible pour le canal asynchrone, c’est pour cela que notre code commence réellement. Écrivons tout cela avant d’oublier quoi que ce soit:

Complétion automatique implémentée de manière réactive

Ce n’était pas si mal. Et comme cela est réactif, nous pouvons maintenant désactiver la détection des changements pour ce composant et nous ne téléchargerons pas de données pour les termes de recherche précédents. Cela garantit que les résultats ne seront jamais désynchronisés avec le terme recherché. (Voici le projet StackBlitz avec les 2 implémentations.)

Aurions-nous pu arriver à cette solution un peu plus en douceur, sans avoir à garder autant dans notre esprit à la fois?

Lorsque nous comparons ces deux solutions côte à côte, nous constatons que les solutions impératives et réactives associent différentes étapes du processus:

La programmation impérative associe l'événement à l'effet immédiat, tandis que la programmation réactive associe l'effet final à sa source immédiate de données. Ben Lesh et d'autres ont répété à quelques reprises que «penser de manière réactive» revenait à penser en arrière ou à l'envers. Si vous examinez la solution réactive et commencez par la fin, vous remarquerez que la partie publique $ = pourrait avoir été écrite sans même tenir compte du gestionnaire d’événements (change).

La clé consiste donc à penser d'abord au consommateur final, qui est le canal asynchrone dans le modèle. Il veut les données filtrées, vous commencez donc par data $ =. La source immédiate des données filtrées, searchTerm => fetchData (searchTerm) nécessite un terme de recherche. Sans nous soucier de la provenance du terme recherché, nous supposerons qu’il existe une observable que nous pouvons chaîner. Nous allons donc simplement écrire searchTerm $ avant de l’avoir défini et l’en chaîner avec un switchMap (). Ensuite, nous pouvons déterminer où ces termes de recherche vont être pompés dans cet observable en configurant le gestionnaire d'événements pour (change). Ainsi, en réfléchissant en arrière, nous sommes en mesure de concevoir une solution avec un élément de moins à suivre à la fois.

Encapsulons ce processus en deux étapes:

  1. Identifier le consommateur
  2. Nommer un observable à enchaîner, sans se soucier de ce qui va mettre des valeurs en elle
  3. Connectez le consommateur avec ce nouveau observable
  4. Définir le premier observable et d'où il tire ses propres valeurs

Si nous devons gérer plusieurs événements asynchrones se produisant en série, nous pouvons répéter les étapes 1 à 3 pour chaque chaîne observable, jusqu'à atteindre le tout premier observable (étape 4).

Maintenant, je veux essayer ce processus sur un problème beaucoup plus complexe. Mais cela devient vraiment long, alors je vais laisser ça pour mon prochain post.

Conclusion

La programmation réactive peut nous aider à éviter de nombreux bugs coûteux, mais être à l'aise avec cela est un obstacle majeur. Je pense que la communauté du développement Web continuera à découvrir des solutions qui faciliteront beaucoup la programmation réactive, ce qui épargnera aux développeurs beaucoup de frustration, de temps et de ressources.

Merci d'avoir lu! Si vous avez vu mon dernier message, vous vous souviendrez peut-être à la fin que j'avais dit que mon prochain message en tirerait parti. Je travaillais sur ce poste lorsque je me suis laissé distraire. Donc, cet autre message est toujours à venir.

Quoi qu'il en soit, s'il vous plaît partagez vos pensées dans les commentaires! Quels autres outils connaissez-vous? Que souhaitez-vous voir? Avez-vous utilisé un processus comme celui que j'ai décrit ici? Quels autres conseils avez-vous?