Gestion de la complexité

La simplicité d'une application Web peut s'avérer étonnamment compliquée. Dans ce module, vous découvrirez comment les API Web fonctionnent avec les threads et comment vous pouvez l'utiliser pour les modèles de PWA courants, tels que la gestion de l'état.

Dans sa vidéo Simple Made Easy, Rich Hickey discute des qualités des choses simples par rapport aux choses complexes. Il décrit les choses simples comme se concentrant sur:

"Un rôle, une tâche, un concept ou une dimension."

Il souligne toutefois que la simplicité ne concerne pas les éléments suivants:

"Avoir une instance ou effectuer une seule opération."

La simplicité d'un produit dépend de son interconnexion.

La complexité vient de la reliure, du tissage ou, selon le terme de Rich, de la combinaison de choses. Vous pouvez calculer la complexité en comptant le nombre de rôles, de tâches, de concepts ou de dimensions gérés par quelque chose.

La simplicité est essentielle dans le développement de logiciels, car un code simple est plus facile à comprendre et à gérer. La simplicité est également nécessaire pour les applications Web, car elle peut contribuer à rendre nos applications rapides et accessibles dans tous les contextes possibles.

Gérer la complexité des PWA

Tout le code JavaScript que nous écrivons pour le Web touche le thread principal à un moment donné. Le thread principal, cependant, présente une grande complexité par défaut que vous, en tant que développeur, ne pouvez pas contrôler.

Le thread principal est le suivant:

  • Chargé de dessiner la page, qui est elle-même un processus complexe en plusieurs étapes impliquant le calcul des styles, la mise à jour et la composition des calques, et la peinture à l'écran.
  • Il est chargé d'écouter et d'interagir avec des événements, y compris des événements tels que le défilement.
  • Charge le chargement et le déchargement de la page.
  • Gestion des contenus multimédias, de la sécurité et des identités C'est tout avant que le code que vous écrivez puisse s'exécuter sur ce thread, par exemple:
  • Manipulation du DOM
  • Accès à des API sensibles (fonctionnalités des appareils, contenus multimédias, sécurité, identité, etc.)

Comme l'a dit Surma dans sa conférence sur le Chrome Dev Summit 2019, le fil de discussion principal est surmené et sous-payé.

Pourtant, la plupart du code d'application se trouve également dans le thread principal.

Tout ce code augmente la complexité du thread principal. Le thread principal est le seul que le navigateur peut utiliser pour mettre en page et afficher le contenu à l'écran. Par conséquent, lorsque votre code nécessite de plus en plus de puissance de traitement, nous devons l'exécuter rapidement. En effet, chaque seconde qu'il faut pour exécuter la logique d'application est une seconde que le navigateur ne peut pas répondre à l'entrée utilisateur ni redessiner la page.

Lorsque les interactions ne se connectent pas aux entrées, lorsque les frames s'arrêtent ou lorsqu'il faut trop de temps pour utiliser un site, les utilisateurs sont frustrés, ils ont le sentiment que l'application ne fonctionne pas et leur confiance diminue.

La mauvaise ? Ajouter de la complexité au fil de discussion principal est un moyen presque infaillible de rendre ces objectifs difficiles. La bonne nouvelle ? Parce que ce que le thread principal doit faire est clair: il peut être utilisé comme guide pour réduire sa dépendance pour le reste de votre application.

Séparation des tâches

Les applications Web peuvent effectuer de nombreux types de tâches différentes, mais d'une manière générale, vous pouvez les décomposer en tâches qui touchent directement l'interface utilisateur et celles qui ne le sont pas. Le fonctionnement de l'interface utilisateur consiste à:

  • Appuyez directement sur le DOM.
  • Utilise des API qui accèdent aux fonctionnalités de l'appareil, par exemple les notifications ou l'accès au système de fichiers.
  • Concerne l'identité, par exemple les cookies utilisateur, le stockage local ou de session.
  • Il gère les contenus multimédias, par exemple les images, l'audio ou la vidéo.
  • A des implications sur la sécurité qui nécessiteront l'intervention de l'utilisateur pour l'approbation, comme l'API Web Serial.

Les tâches qui ne sont pas liées à l'interface utilisateur peuvent inclure des éléments tels que:

  • Des calculs pures.
  • Accès aux données (extraction, base de données indexée, etc.).
  • Crypto.
  • Messages
  • Création ou manipulation de blobs ou de flux.

Les tâches qui ne sont pas liées à l'interface utilisateur sont souvent réservées par des tâches de l'interface utilisateur: un utilisateur clique sur un bouton qui déclenche une requête réseau pour une API qui renvoie des résultats analysés, qui sont ensuite utilisés pour mettre à jour le DOM. Lors de l'écriture de code, cette expérience de bout en bout est souvent prise en compte, mais l'emplacement de chaque partie de ce flux ne l'est généralement pas. Les limites entre les tâches de l'interface utilisateur et celles qui ne le sont pas sont aussi importantes à prendre en compte que les expériences de bout en bout, car elles constituent le premier moyen de réduire la complexité du thread principal.

Se concentrer sur une seule tâche

L'un des moyens les plus simples de simplifier le code consiste à décomposer les fonctions afin que chacune d'elles se concentre sur une seule tâche. Les tâches peuvent être déterminées par les limites identifiées en parcourant l'expérience de bout en bout:

  • Tout d'abord, répondez à l'entrée utilisateur. Il s'agit d'un fonctionnement de l'interface utilisateur.
  • Ensuite, envoyez une requête API. Il ne s'agit pas d'un travail dans l'interface utilisateur.
  • Ensuite, analysez la requête API. Là encore, il ne s'agit pas d'un travail dans l'interface utilisateur.
  • Déterminez ensuite les modifications à apporter au DOM. Il peut s'agir d'un fonctionnement de l'interface utilisateur. Si vous utilisez une implémentation de DOM virtuel, par exemple, il ne s'agit peut-être pas d'une interface utilisateur.
  • Enfin, apportez des modifications au DOM. Il s'agit d'un fonctionnement de l'interface utilisateur.

Les premières limites sont clairement définies entre les tâches de l'interface utilisateur et celles qui ne le sont pas. Ensuite, des appels de jugement doivent être effectués: est-ce qu'effectuer et analyser une requête API est une ou deux tâches ? Si les modifications DOM ne concernent pas l'interface utilisateur, sont-elles groupées avec le travail de l'API ? Dans le même fil de discussion ? Dans un autre fil de discussion ? Le niveau de séparation approprié ici est essentiel pour simplifier votre codebase et pouvoir en retirer des parties du thread principal.

Composabilité

Pour décomposer les workflows de bout en bout volumineux en parties plus petites, vous devez réfléchir à la composabilité de votre codebase. Pour vous inspirer de la programmation fonctionnelle, prenez en compte les éléments suivants:

  • Catégoriser les types de tâches effectuées par votre application
  • Créer des interfaces d'entrée et de sortie communes pour eux

Par exemple, toutes les tâches de récupération d'API reçoivent le point de terminaison de l'API et renvoient un tableau d'objets standards, tandis que toutes les fonctions de traitement des données reçoivent et renvoient un tableau d'objets standards.

JavaScript utilise un algorithme de clonage structuré destiné à copier des objets JavaScript complexes. Les web workers l'utilisent lorsqu'ils envoient des messages, et IndexedDB s'en sert pour stocker des objets. En choisissant des interfaces que vous pouvez utiliser avec l'algorithme de clonage structuré, vous rendez leur exécution encore plus flexible.

Ainsi, vous pouvez créer une bibliothèque de fonctionnalités modulables en catégorisant votre code et en créant des interfaces d'E/S courantes pour ces catégories. Le code composable est la marque de fabrique des codebases simples: des éléments interchangeables faiblement couplés et interchangeables, qui peuvent se placer "l'un à côté de l'autre" et s'appuyer les uns sur les autres, contrairement au code complexe qui est profondément interconnecté et ne peut donc pas être facilement séparé. Sur le Web, le code composable peut faire la différence entre un surtravail du thread principal ou non.

Avec le code composable en main, il est temps d'en retirer une partie du thread principal.

Utiliser des workers Web pour réduire la complexité

Les nœuds de calcul Web, une fonctionnalité Web souvent sous-utilisée, mais largement disponible, vous permettent de déplacer des tâches en dehors du thread principal.

Les nœuds de calcul Web autorisent une PWA à exécuter (certaines) JavaScript en dehors du thread principal.

Il existe trois types de nœuds de calcul.

Les nœuds dédiés, qui sont généralement utilisés pour décrire les workers, peuvent être utilisés par un seul script dans une seule instance en cours d'exécution d'une PWA. Dans la mesure du possible, toute tâche qui n'interagit pas directement avec le DOM doit être déplacée vers un collaborateur Web pour améliorer les performances.

Les nœuds de calcul partagés sont semblables aux nœuds de calcul dédiés, à la différence que plusieurs scripts peuvent les partager dans plusieurs fenêtres ouvertes. Cela offre les avantages d'un nœud de calcul dédié, mais avec un état et un contexte interne partagés entre les fenêtres et les scripts.

Un nœud de calcul partagé peut, par exemple, gérer l'accès et les transactions pour l'indexedDB d'une PWA, et diffuser les résultats des transactions sur tous les scripts d'appel pour leur permettre de réagir aux modifications.

Le dernier Web worker est abordé en détail dans ce cours: les service workers qui servent de proxy pour les requêtes réseau et sont partagés entre toutes les instances d'une PWA.

Essayez par vous-même

C'est l'heure du code ! Créez entièrement une PWA en vous appuyant sur tout ce que vous avez appris dans ce module.

Ressources