Recherche/Créer et modifier des machines à états pilotées par les événements sans casser leur comportement

Créer et modifier des machines à états pilotées par les événements sans casser leur comportement

Pour une bibliothèque de machines à états, l’IA termine chaque événement avant le suivant, gère les erreurs sans sauter le nettoyage, protège threads et machines enfants, écrit d’abord des tests pour moteurs synchrones et asynchrones et aligne code, doc et schémas.

Pour utiliser ces règles d'IA
## Décider quel module gère chaque partie de la logique de la machine à états Cartographiez les responsabilités avant de modifier le code afin que le comportement reste dans le composant qui détient les connaissances nécessaires. ### Définir des limites claires - Séparez les définitions d’états et d’événements, la composition des transitions, l’acheminement des fonctions de rappel, l’adaptation des signatures, les moteurs d’exécution, les registres, les intégrations et la sérialisation. - Maintenez l’infrastructure générale, comme l’acheminement et l’adaptation des signatures, indépendante du domaine des machines à états. - Donnez à chaque module, classe et fonction une seule raison claire de changer. - Préférez de petits modules ciblés aux interfaces explicites plutôt que d’étendre par commodité un module sans rapport. ### Placer la logique délibérément - Appliquez le principe Information Expert : calculez un résultat là où les connaissances nécessaires existent déjà, puis renvoyez-le ou signalez-le au lieu de le recalculer ailleurs. - Appliquez la Law of Demeter : transmettez la valeur précise dont un collaborateur a besoin plutôt que son objet parent. - Appliquez Interface Segregation : acceptez un champ ou un protocole restreint lorsqu’une fonction auxiliaire n’a pas besoin de l’objet complet. - Préservez le comportement public lors d’une refactorisation et identifiez les tests qui le caractérisent avant de déplacer la logique. ## Terminer le traitement d’un événement avant de passer au suivant Modélisez le traitement des événements à deux niveaux distincts et ne les fusionnez pas pendant l’implémentation. ### Distinguer les niveaux de traitement - Une micro-étape exécute atomiquement un ensemble de transitions sélectionné dans l’ordre défini : fonctions de rappel préalables, fonctions de sortie, actions de transition, fonctions d’entrée et fonctions de rappel ultérieures. - Une macro-étape traite un événement externe et répète les micro-étapes jusqu’à ce que la machine atteigne une configuration stable. - Une configuration n’est stable que lorsqu’aucune transition sans événement n’est activée et que la file interne est vide. ### Rendre explicite la sémantique des files et des événements - Les événements envoyés de l’extérieur entrent dans la file externe et attendent la fin de la macro-étape en cours. - Les événements déclenchés en interne entrent dans la file interne et s’exécutent dans la macro-étape en cours avant les événements externes ultérieurs. - Une déclaration de transition non affectée peut représenter une transition sans événement qui se déclenche lorsque sa condition de garde devient vraie. - Une transition affectée crée un événement nommé ; si des alias sont dérivés de conventions de nommage, un identifiant explicite est prioritaire. - Testez séparément l’ordre des files, la progression sans événement et l’arrêt sur une configuration stable. ## Traiter les erreurs comme des événements sans sauter les fonctions de finalisation Définissez où les erreurs sont interceptées et quelles fonctions de rappel du cycle de vie continuent de s’exécuter après un échec. ### Définir la limite de gestion des erreurs - Rendez explicite, pour chaque type de machine, la politique qui consiste à intercepter les erreurs comme des événements ; les variantes orientées diagramme et les variantes de base peuvent intentionnellement adopter des valeurs par défaut opposées. - Interceptez les erreurs au niveau du bloc d’action plutôt que de traiter une micro-étape entière comme une seule limite d’interception, et exécutez malgré tout les fonctions de finalisation en cas de réussite comme d’échec lorsque le cycle de vie les garantit. - Acheminez une erreur d’exécution interceptée sous forme d’événement interne afin que le diagramme d’états puisse la traiter par les transitions déclarées. - Empêchez les boucles de défaillances récursives en supprimant ou en interrompant les erreurs déclenchées pendant le traitement de l’événement interne d’erreur d’exécution. ### Préserver les distinctions entre fonctions de rappel - Séparez les fonctions de rappel génériques du cycle de vie de celles propres à un événement ou à un état. - Une fonction de rappel nommée `error` n’est pas automatiquement générique ; exigez la transition d’erreur correspondante avant de l’appeler par convention. - Si les actions de transition désactivent l’interception locale des erreurs pendant le traitement de l’événement d’erreur, gardez cette règle distincte des blocs d’entrée et de sortie, qui utilisent toujours leur gestionnaire d’erreurs. - Testez indépendamment la réussite, l’échec ordinaire, l’échec dans la gestion des erreurs et l’ordre de finalisation. ## Protéger les threads et les machines enfants quand on modifie les files ou les verrous Préservez les garanties de sûreté des threads et d’acheminement des invocations lorsque vous modifiez les files, les verrous ou la gestion des machines enfants. ### Maintenir un traitement concurrent sûr - N’autorisez au plus qu’un seul thread à exécuter les transitions d’une instance de machine à la fois. - Utilisez une file de priorité sûre pour les threads lorsque des émetteurs concurrents sont pris en charge. - Ne remplacez pas une file sûre pour les threads par une simple liste ou une file à double extrémité non sûre sans reconcevoir et démontrer le contrat de concurrence. - Soumettez les situations réelles de contention à des tests de charge avec des threads émetteurs coordonnés et des barrières. - Dans les tests de threads bloquants, utilisez des attentes d’événements interruptibles avec des délais d’expiration explicites plutôt que des pauses fixes susceptibles de laisser des threads actifs pendant le nettoyage. ### Gérer précisément les enfants invoqués - Ne supprimez une invocation enfant que lorsqu’elle est à la fois terminée et annulée. - Maintenez une machine enfant terminée mais non annulée accessible au routage tant que son état propriétaire reste actif. - Si la construction d’un enfant bloque dans la boucle de traitement, capturez sa référence au moyen d’un écouteur précoce du cycle de vie avant que la boucle ne continue. - Acheminez les événements destinés à un enfant par le gestionnaire d’invocations et le gestionnaire de l’enfant plutôt que de contourner le contrôle du cycle de vie. - Testez l’annulation, l’arrêt naturel, le maintien du routage et la livraison tardive d’événements comme des cas distincts. ## Lancer les tests dans l’environnement fixé du projet et mesurer la couverture des branches Exécutez l’installation, les tests, les contrôles qualité et la documentation au moyen de l’environnement verrouillé et de l’exécuteur de tâches du projet. ### Exécuter les tests en toute sécurité - Imposez un délai d’expiration explicite à l’ensemble de la suite pour révéler les threads restés actifs ou les boucles infinies. - Utilisez l’exécution parallèle prise en charge pour la suite complète lorsque les tests comportent des attentes intentionnelles ou des délais d’expiration de secours. - Lancez la suite de référence depuis la racine du projet lorsqu’une exécution limitée à un chemin omettrait les doctests des modules ou de la documentation. - Analysez les exécutions qui dépassent la référence établie au lieu de les relancer sans cesse et sans diagnostic. - N’activez la journalisation détaillée du débogage que pour des investigations ciblées lorsque la journalisation globale modifie les mesures de performance. - Capturez une seule fois les sorties volumineuses, puis analysez le résultat enregistré au lieu de relancer plusieurs fois la même suite coûteuse. ### Mesurer la couverture prévue - Activez explicitement la collecte de couverture lorsque cela est nécessaire, puis générez pour les modules concernés un rapport des lignes manquantes ou lisible par machine ; choisir seulement un format de rapport peut ne pas activer la collecte. - Vérifiez la couverture des branches, et pas uniquement celle des lignes, avant le commit. ## Écrire d’abord un test qui échoue et le lancer sur des moteurs synchrones et asynchrones Traitez les tests comme des exigences d’implémentation plutôt que comme un nettoyage effectué après l’écriture du code. ### Suivre une boucle pilotée par les tests 1. Identifiez les nouvelles branches, les cas limites, les chemins d’erreur et les risques de concurrence pendant la planification. 2. Ajoutez un test ciblé qui échoue en raison du comportement manquant ou incorrect. 3. Implémentez le changement de comportement minimal qui fait réussir le test. 4. Exécutez le test ciblé, la couverture du module concerné et les contrôles plus larges requis. 5. Ne refactorisez qu’une fois le comportement et la couverture protégés. ### Réutiliser l’infrastructure de test - Utilisez un exécuteur paramétré partagé pour tester le même diagramme d’états avec des moteurs synchrones et asynchrones. - Laissez l’exécuteur abstraire le démarrage des moteurs et la livraison des événements au lieu de dupliquer les classes de test ou d’ajouter des écouteurs sans effet. - Utilisez des fixtures pour les chemins temporaires, les changements d’environnement et l’état mutable ; ne codez pas les chemins en dur et ne dépendez pas de variables globales mutables. - Couvrez les branches défensives par des tests dès qu’elles sont accessibles ; n’excluez une branche de la couverture que lorsque son impossibilité est établie. - Maintenez le seuil requis de couverture des branches imposé par les contrôles de commit standard. ## Lancer les contrôles du code et garder l’analyseur et le schéma alignés Appliquez un système de qualité cohérent à l’implémentation, aux exemples, aux contrats générés et à la documentation destinée aux développeurs. ### Faire respecter les conventions de code - Exécutez le formateur, le linter, le trieur d’imports, le vérificateur de types et les tests du projet au moyen de la chaîne d’outils de référence. - Préférez des imports de premier niveau triés ; n’utilisez les imports différés que lorsqu’ils sont nécessaires pour rompre une dépendance circulaire. - Respectez les conventions de nommage et de docstring du projet, et documentez les sémantiques cachées plutôt que de répéter des noms ou des types évidents. - Utilisez une docstring d’attribut directement sous l’attribut lorsque le générateur de documentation l’exige ; réservez les commentaires en ligne à la logique non évidente. ### Tester la documentation et les formats déclaratifs - Rendez les exemples de documentation exécutables sous forme de doctests lorsque c’est possible. - Placez les exemples qui nécessitent une concurrence réelle dans des tests unitaires et référencez-les depuis la documentation. - Maintenez synchronisés l’analyseur, le comportement à l’exécution et le schéma publié chaque fois que le vocabulaire déclaratif change. - Considérez les ensembles de clés acceptées par l’analyseur comme la source de vérité et testez-les par rapport aux propriétés correspondantes du schéma. - Validez chaque exemple structuré et chaque fixture par rapport au schéma afin que toute divergence entre l’analyseur et le schéma fasse échouer la suite de tests. ## Terminer les changements sans contourner les hooks de commit ni committer de secrets Utilisez le processus de contribution du dépôt pour préserver la traçabilité, les artefacts générés et les limites de sécurité. ### Franchir le contrôle qualité - Ciblez la branche de développement désignée du dépôt et respectez sa convention de messages de commit. - Utilisez des commits signés lorsque le projet les privilégie. - Ne contournez jamais les hooks de commit et ne désactivez pas les contrôles requis. - Si un hook régénère ou modifie des fichiers, examinez ces résultats et ajoutez-les à l’index, puis validez à nouveau jusqu’à ce que tous les hooks réussissent. - Examinez le diff final et confirmez que l’implémentation, les tests, la documentation et les contrats générés concordent. ### Protéger les limites de confiance - N’intégrez jamais de secrets, d’informations d’identification ou de fichiers d’environnement dans un commit. - Validez les données non fiables aux limites du système et rendez les invariants internes explicites. - N’affaiblissez pas la sûreté des files, la prévention des boucles d’erreur, la validation des schémas ou l’application de la couverture uniquement pour faire accepter un changement. - Signalez tout contrôle requis qui n’a pas pu être exécuté et ne prétendez pas que le travail est terminé sans résultats directs.