Swift Concurrency Series 08|Organisation du code asynchrone dans des projets réels
Ce qui est vraiment difficile, c'est de savoir si l'ensemble du lien asynchrone peut rester clair pendant une longue période.
Lorsqu’il n’y a qu’une ou deux requêtes asynchrones dans le projet, une grande partie du code n’a pas l’air trop mauvaise. Un véritable tournant se produit généralement lorsque les situations suivantes se produisent simultanément :
- La même page comporte un premier chargement, une actualisation déroulante, une nouvelle tentative et un changement de filtre
- Le cache local et les requêtes distantes doivent être impliqués ensemble
- En quittant la page, annulez certaines tâches et conservez d’autres
- Plusieurs modules partagent certaines ressources simultanées
A cette époque, vous constaterez que la plus grande difficulté du code asynchrone est déjà :
Comment organiser l’ensemble du lien asynchrone pour qu’il ne devienne pas plus dispersé et difficile à modifier au fur et à mesure de son écriture.
Dans cet article, je ne veux pas parler d’une seule API, mais je veux parler des principes d’organisation que j’apprécie davantage dans les projets réels.
1. Ce que je partage en premier, c’est la responsabilité.
Lorsque le code asynchrone est perturbé, la première réaction est souvent de « démanteler la fonction ». Le fractionnement des fonctions est certainement utile, mais si les responsabilités sont déjà mélangées, les diviser implique généralement simplement de diviser le désordre en plusieurs petits fichiers.
Je suis plus préoccupé par la séparation des responsabilités en premier. Généralement divisé en au moins trois couches :
1. Couche de page
La couche page est responsable de :
- Déclencher l’intention de l’utilisateur
- Afficher l’état de la page
- Répondre aux changements interactifs
Il sait « ce qui doit être chargé maintenant », mais il ne devrait pas être responsable de « comment orchestrer l’ensemble du processus asynchrone ».
2. Couche d’état / ViewModel
La couche étatique est chargée de :
- Traduire l’intention de l’utilisateur en tâches
- Décider si les tâches doivent être parallélisées, remplacées ou annulées
- Gérer la sémantique des pages telles que le chargement, le chargement, l’échec, etc.
- Déterminer quels résultats sont encore éligibles pour réécrire la page
C’est le véritable point final des processus asynchrones.
3. Couche de service
La couche service est responsable de :
- Ajuster l’interface
- lire le cache
- Combiner plusieurs sources de données
- Fournir des capacités de domaine
Il ne devrait pas savoir à quoi ressemble la page, ni se faufiler dans la sémantique de l’état de l’interface utilisateur.
Une grande partie du code asynchrone est perturbée car une fois ces trois couches mélangées, tout changement dans les exigences affectera en même temps l’interface utilisateur, le processus, le statut et la source de données.
2. Le principe le plus important de la couche page : en savoir moins sur les détails asynchrones
Je n’aime pas que la page effectue elle-même trop d’étapes asynchrones. Parce qu’une fois que la page en saura trop, elle commencera à prendre en charge ces choses :
- Demander un contrôle de séquence
- Des erreurs
- Filtrer les résultats
- Politique d’annulation
- chargement de la sémantique des subdivisions
Lors de la modification d’une exigence comme celle-ci, l’interface utilisateur et le processus sont souvent affectés ensemble.
Je préfère donc que la page exprime uniquement ces choses :
- “Je dois me rafraîchir maintenant”
- “L’utilisateur a cliqué pour réessayer”
- “Conditions de filtrage modifiées”
Quant à ce qu’il y a derrière :
- Lire d’abord le cache ou ouvrir d’abord l’interface ?
- Voulez-vous arrêter les anciennes tâches ?
- Si le résultat est expiré
- Le premier chargement et actualisation doivent-ils partager le même lien ?
Mettez autant que possible dans la fermeture de la couche d’état.
3. Le statut de la page doit être explicite, ne comptez pas sur un tas d’éléments dispersés pour épeler la sémantique.
De nombreuses pages asynchrones ressembleront à ceci ultérieurement :
isLoadingisRefreshinghasErrorshowRetryisEmptyitems
Ces valeurs sont raisonnables lorsqu’elles sont considérées individuellement, mais sont sujettes à des contradictions lorsqu’elles sont combinées :
- Chargement avec d’anciennes erreurs
- Les deux affichent l’état vide et conservent l’ancienne liste
- Rafraîchissant, mais le logo du premier chargement est toujours là
J’accorde donc plus d’importance à « l’état sémantique de la page » qu’à « de nombreux petits états qui peuvent être librement combinés ».
Parce que ce qui est vraiment important dans une page asynchrone est de savoir si elle peut indiquer clairement à quelle étape se trouve actuellement la page.
4. Les limites des tâches doivent être claires, sinon tout sera simplement “exécuté en premier”
Que la structure asynchrone soit stable ou non dépend de sa capacité à répondre à ces questions :
- À qui appartient cette mission ? -Est-ce que ça continue quand la page est quittée ?
- Lorsqu’une nouvelle tâche arrive, l’ancienne tâche deviendra-t-elle immédiatement invalide ?
- S’agit-il d’une tâche autonome ou d’une partie d’un processus plus long ?
S’il n’est pas possible de répondre clairement à ces questions, le code suivant entrera définitivement dans un état :
- Partout semble être explicite
- Mais personne ne peut reconstituer un récit complet du fonctionnement de ce lien.
Si un morceau de code asynchrone est difficile à répéter en langage naturel, il est généralement difficile de le maintenir de manière stable par la suite.
5. Je ferai de mon mieux pour intégrer la « validité des résultats » dans le processus
De nombreuses pages sont foirées. En apparence, il semble que le résultat ait échoué, mais il est en réalité plus proche :
- Les anciens résultats ont été renvoyés avec succès
- Mais cela ne correspond plus au contexte de la page actuelle
Ce type de problème est particulièrement susceptible de se produire lorsque :
- Recherche -Interrupteur de filtre
- Pagination
- Accédez rapidement à la page de sortie
Si l’efficacité des résultats n’est pas prise en compte dans la conception, la page développera tôt ou tard ces étranges phénomènes :
- Restauration du contenu
- le chargement se termine inexplicablement
- Les invites d’erreur écrasent l’état de réussite actuel
Je me soucie donc de :
- Qui jugera si le résultat est expiré ?
- Si chaque point d’appel doit être jugé par vous-même
- Fermez toujours l’interface uniformément au niveau de la couche d’état
Mes préférences sont très claires : **Essayez d’être cohérent dans votre conclusion et ne laissez pas chaque branche de vue juger par elle-même « si le résultat cette fois compte toujours ». **
6. Ne pas introduire la sémantique des pages dans la couche de service
Beaucoup de code est foiré. En apparence, il semble que la couche de service ne fera pas de requêtes. En fait, il est plus proche de la couche de service et commence lentement à intégrer les concepts de page.
Par exemple, un nom ou une logique comme celle-ci commence à apparaître dans la couche de service :
- “Demande spéciale pour le premier chargement de la page d’accueil”
- “La page de détails est vide et pleine de logique”
- “Mauvaise rédaction pour cette page”
Une fois ces éléments mélangés, les structures ultérieures deviendront de plus en plus difficiles à réutiliser. Étant donné que la couche de service commence à savoir à quoi ressemble la page et que la couche de page commence à connaître les détails d’implémentation de la couche de service, les limites sont rapidement floues.
La couche service est plus adaptée pour se concentrer sur :
-Quelles données avez-vous obtenues ?
- Comment combiner les sources de données
- Quelle est la stratégie de mise en cache ?
Plutôt que « comment cette page doit-elle se comporter ? »
7. Un principe m’importe : le processus asynchrone doit être clairement récité
C’est un critère couramment utilisé lorsque je fais moi-même des revues de code.
Si vous récupérez un morceau de code asynchrone, il est déjà difficile de le répéter en langage naturel :
- Quelle est l’entrée ?
- Quel est le processus principal ?
- Quelles tâches seront annulées ?
- Quels résultats seront rejetés
- Quels états sont modifiés par quelle couche ?
Même si ce code peut fonctionner aujourd’hui, il deviendra probablement de plus en plus difficile à faire évoluer à l’avenir.
La vraie crainte du code asynchrone est qu’il n’est pas clair. Une fois que vous ne pouvez pas le dire clairement, les itérations ultérieures ne sont qu’une question de chance.
8. Une idée organisationnelle plus proche de la mise en œuvre
Si je voulais le résumer dans une phrase plus pratique, je l’organiserais ainsi :
- Couche de page : exprimer l’intention
- Couche d’état : tâches de fermeture et sémantique d’état
- Couche de service : fourniture de fonctionnalités
- Couche de ressources partagées : utilisez Actor ou d’autres moyens d’isolation pour gérer l’état partagé si nécessaire
Clarifiez ensuite ces relations dans la couche d’état :
- Quelles tâches s’excluent mutuellement
- Quelles tâches sont parallèles
- Quels résultats peuvent être rejetés
- Quels états doivent être mis à jour par l’acteur principal
Une situation courante est que cela « ressemble à une couche supplémentaire », mais dans les projets vraiment complexes, ces couches servent à empêcher la logique asynchrone de se propager directement à chaque événement de page.
9. Conclusion : Le cœur de l’organisation asynchrone dans les projets réels est la frontière
La chose la plus importante dans un projet réel n’est jamais de savoir s’il s’agira d’un Task, d’un async let ou d’un Actor.
Ce qui détermine réellement si le code peut évoluer sur le long terme, c’est si ces limites sont claires :
- Limite entre la couche de page et la couche de processus
- Frontière entre la couche d’état et la couche de service
- Frontière entre l’état partagé et l’état normal
- La frontière entre les résultats valides actuels et les résultats expirés
Alors si je devais résumer cet article en une phrase, je dirais :
La clé de l’organisation du code asynchrone dans les projets réels n’est pas « comment écrire une API asynchrone », mais « si les quatre limites des tâches, du statut, des résultats et des responsabilités sont clairement définies ».
Ce n’est que lorsque ces limites seront claires que le code asynchrone pourra véritablement passer de « peut s’exécuter » à « peut être maintenu pendant une longue période ».
What to read next
Want more posts about Swift Concurrency?
Posts in the same category are usually the best next step for reading more on this topic.
View same categoryWant to keep following #Architecture?
Tags are useful for related tools, specific problems, and similar troubleshooting notes.
View same tagWant to explore another direction?
If you are not sure what to read next, return to the homepage and start from categories, topics, or latest updates.
Back home