


Modifier une grande application média de bureau sans figer l’UI ni casser l’historique d’annulation
Pour une grande application de bureau qui gère fichiers et médias : l’IA sort le travail lourd du thread d’UI, gère les annulations et les cas limites, garde un historique d’annulation exact et ne signale que les tests vraiment lancés.
Pour utiliser ces règles d'IA
## Suivre tout le chemin d’appel et donner un seul propriétaire à chaque état
- Comprendre la fonctionnalité propriétaire et les abstractions existantes avant de modifier le code, et retracer le chemin d'appel complet à travers l'interface utilisateur, l'automatisation, l'annulation (undo), la persistance et le travail en arrière-plan.
- Pour les changements non triviaux, identifier la source de vérité, le propriétaire de l'état, le domaine d'isolation, les invariants, ainsi que le comportement en cas d'échec et d'annulation avant d'écrire le code.
- Concevoir l'architecture avant les détails d'implémentation, et privilégier un changement cohérent plutôt que des correctifs dispersés dans des couches sans rapport entre elles.
- Conserver un seul propriétaire faisant autorité pour chaque élément d'état mutable ; l'état dérivé est calculé ou maintenu par un cache avec un contrat d'invalidation explicite.
- Réutiliser les opérations de domaine existantes ; ne pas ajouter une seconde implémentation simplement parce que l'appelant est un aperçu, une vue, un contrôleur, un outil d'automatisation ou un test.
- Placer le code avec la fonctionnalité qui en est propriétaire, réserver les utilitaires partagés à l'infrastructure utilisée par plusieurs fonctionnalités indépendantes, et nommer les types et fichiers de façon à rendre la propriété évidente.
## Garder des commentaires courts et retirer le code mort
- Limiter les commentaires au minimum : n'en écrire un que lorsque la raison, l'invariant, la contrainte de sécurité ou le contournement n'est pas évident, et le limiter à une courte ligne.
- Ne pas commenter le code pas à pas, reformuler les noms, décrire le correctif en cours, laisser des traces de suppression, ni conserver du code commenté.
- Préférer des noms précis, des types réduits et des opérations extraites plutôt que des commentaires explicatifs.
- Un calcul ou une règle métier possède une source de vérité unique ; ne jamais la copier dans un autre fichier ou une autre surface.
- Supprimer le code mort, l'état inutilisé, les chemins de compatibilité obsolètes et les diagnostics temporaires avant de terminer.
- Ne pas ajouter de code de compatibilité pour des plateformes, versions ou configurations que le projet ne prend pas en charge.
## Sortir le travail lourd du thread d’UI et revérifier l’état après chaque await
- Traiter le thread d'interface utilisateur comme une ressource rare : il peut porter l'état de l'interface et une coordination légère, mais ne doit jamais effectuer d'opérations sur les fichiers, de décodage, d'inférence de modèle, de traitement d'image, d'indexation, d'export, d'appels bloquants ou de transformations lourdes.
- L'isolation n'est pas la performance : marquer un traitement comme asynchrone ou l'encapsuler dans une tâche ne le déplace pas hors du thread courant. Il faut suivre l'exécuteur réel.
- Rendre les changements de thread explicites ; capturer l'entrée immuable minimale avant de quitter un thread, renvoyer une valeur, puis l'appliquer après avoir confirmé qu'elle est toujours valide.
- Après chaque point de suspension, supposer que l'état partagé a changé : revalider l'identité, la génération, la sélection et les préconditions avant de valider un résultat.
- Privilégier la concurrence structurée ; une tâche non structurée doit avoir un propriétaire clair, une référence conservée lorsque l'annulation compte, et un démontage bien défini.
- L'annulation est coopérative : les boucles longues doivent la vérifier à des points utiles et ne jamais valider de résultats partiels ou obsolètes.
- Limiter le travail parallèle en fonction de la ressource réellement rare, dédupliquer les requêtes identiques en cours, acquérir et libérer les verrous dans des portées correspondantes, et reprendre chaque continuation ou callback exactement une fois.
- Ne jamais bloquer le thread d'interface utilisateur avec des attentes synchrones, des sémaphores, des verrous ou du polling en attendant un travail asynchrone.
## Traiter les fichiers hors du thread d’UI et les remplacer de façon atomique
- Toute opération sur le système de fichiers s'exécute hors du thread d'interface utilisateur : lectures, écritures, vérifications d'existence et de métadonnées, énumération, copie, déplacement, remplacement, suppression et création de répertoires.
- Considérer que tout volume peut être lent, amovible, modifié de l'extérieur ou reposer sur un réseau ; une petite taille ou un accès réussi par le passé ne rend pas un accès synchrone sûr pour autant.
- Capturer les données de modèle possédées avant tout travail sur fichier, et ne pas modifier l'état observable depuis l'exécuteur de ce travail.
- Préparer la sortie complète en dehors de la cible active, préparer le remplacement sur la destination, puis l'installer de façon atomique ; faire transiter les écritures actives par un seul coordinateur plutôt que d'écrire directement.
- Sérialiser les opérations ciblant le même fichier ou la même destination afin que sauvegarde, import, génération, miniature, suppression, export et fermeture n'entrent jamais en compétition.
- Utiliser des chemins temporaires uniques et les nettoyer en cas de succès, d'échec ou d'annulation ; ne jamais supprimer ni remplacer une destination tant que le remplacement complet n'est pas prêt.
- Signaler clairement les échecs de fichier demandés par l'utilisateur ; ne pas les masquer derrière des erreurs ignorées, des résultats vides ou des réponses qui ont l'apparence d'un succès.
## Mesurer avant d’annoncer un gain et garder légers les chemins fréquents
- Identifier les chemins sensibles à la performance tels que le rendu, la lecture, la gestion des entrées, la mise à jour des vues, l'import, l'indexation, la sauvegarde, la restauration et l'export, et les traiter comme critiques.
- Ne pas effectuer d'accès au système de fichiers, de journalisation, de sérialisation, d'initialisation de modèle ou de décodeur, ni tout autre réglage bloquant à l'intérieur de chemins exécutés par élément, par image ou de manière répétée.
- Mesurer avant d'affirmer une amélioration : profiler ou benchmarker la même charge de travail avant et après le changement.
- Corriger la complexité algorithmique et le travail inutile avant l'optimisation bas niveau ; surveiller les parcours imbriqués, les tris répétés, les mutations par copie dans une boucle, les allocations intermédiaires et les changements de thread répétés.
- Regrouper et fusionner les mutations en masse, en vidant l'état en attente avant les instantanés, l'annulation, l'export, la fermeture et les lectures qui doivent renvoyer des données à jour.
- Charger et hydrater les données de façon paresseuse, et ne mettre en cache un travail coûteux et réutilisable qu'avec une clé explicite, une capacité, une règle d'invalidation et une politique pour les résultats périmés.
- Réutiliser les objets coûteux lorsque leur cycle de vie le permet, et les invalider lors des changements de configuration pertinents ; éviter qu'un état à haute fréquence comme la progression ou les compteurs n'invalide des vues entières ; limiter la mémoire retenue.
## Lister les cas limites et les chemins d’échec avant d’implémenter
- Se limiter au chemin nominal ne suffit pas : avant d'implémenter, énumérer les cas limites, de cycle de vie, de concurrence et d'échec applicables, et décider quelle couche est responsable de chacun.
- Valider selon le cas les entrées vides, nulles, à zéro, négatives, maximales, en dépassement, non finies, malformées, dupliquées, manquantes, périmées ou non prises en charge.
- Valider avant toute opération arithmétique, indexation ou conversion numérique ; ne jamais compter sur un traitement d'erreur ultérieur pour absorber un piège arithmétique, et définir précisément l'arrondi et l'écrêtage plutôt que d'écrêter silencieusement.
- Couvrir les opérations sans effet et répétées : elles doivent rendre compte fidèlement de leur résultat et ne doivent créer ni mutations, ni entrées d'historique, ni travail dupliqué, ni succès trompeur.
- Prendre en compte l'annulation avant le démarrage, pendant chaque phase, et après la fin du travail mais avant la validation, et prendre en compte les achèvements devenus obsolètes après un changement de sélection, de cible ou de configuration.
- Prendre en compte les chemins de démontage : fermeture, quitter, mise en veille, réveil, désactivation, changement de périphérique, et enregistrement sous un autre nom pendant qu'un travail est actif.
- Préserver les invariants sur chaque chemin d'échec : un succès partiel doit soit pouvoir être repris en toute sécurité et être signalé comme tel, soit être annulé (rollback).
## Faire qu’une action de l’utilisateur s’annule en une seule étape
- Faire transiter les actions utilisateur et les actions automatisées par les mêmes opérations de mutation du domaine et le même historique partagé.
- Une intention utilisateur cohérente produit une seule action annulable ; ne pas exposer les sous-étapes internes comme des entrées d'historique séparées, sauf si chacune a un sens indépendant.
- Valider les arguments et les préconditions avant d'ouvrir un groupe d'historique ; les opérations échouées, annulées, refusées ou sans changement ne doivent pas créer d'entrées vides.
- Le travail imbriqué doit fusionner dans l'action englobante sans fermer les groupes appartenant à la plateforme ou à un autre sous-système.
- L'annulation doit restaurer l'état exact, sans arrondi cumulatif, sans dérive de l'état dérivé, sans relations orphelines et sans sélection obsolète.
- Lorsqu'un changement touche à l'historique, tester l'entrelacement entre les modifications utilisateur, les modifications automatisées, le regroupement d'événements de la plateforme et les requêtes concurrentes.
## Concevoir les outils d’automatisation selon ce que l’utilisateur veut accomplir
- Concevoir les outils à partir de l'intention de l'utilisateur, et non des API internes, des opérations de stockage ou des frontières de service ; définir le résultat visé, les critères de succès, les avertissements, l'échec, l'annulation, l'idempotence et la sémantique d'annulation avant le schéma.
- Un outil exécute une action cohérente ; un appel doit normalement achever un flux de travail atomique, compréhensible et annulable. Ne pas construire un outil généraliste avec des modes sans rapport entre eux, et ne pas obliger les appelants à enchaîner des outils de bas niveau pour un travail que le système peut effectuer lui-même en toute sécurité.
- Exprimer les paramètres avec des concepts de domaine orientés utilisateur, et masquer l'agencement du stockage ainsi que les détails d'implémentation ; utiliser des identifiants d'entité stables pour l'automatisation, en traitant les index positionnels et les libellés comme un simple contexte, non comme une identité durable.
- Traiter chaque argument comme non fiable : exiger des types exacts, des nombres finis, des limites explicites et des identifiants valides ; résoudre et valider la requête complète avant toute mutation, et appliquer les changements portant sur plusieurs entités de façon atomique.
- Réutiliser les mêmes opérations de domaine que l'interface utilisateur, et ne pas dupliquer la logique de placement, de liaison ou de validation.
- Renvoyer des accusés de réception structurés décrivant ce qui a changé, des identifiants stables, un état explicite d'absence d'effet, des avertissements et des erreurs exploitables ; ne jamais renvoyer une réponse ayant l'apparence d'un succès lorsque le résultat a été ajusté ou non atteint, et ne jamais écrêter, réorienter ou revenir à une solution de repli silencieusement, sauf si le contrat le promet et le signale.
- Les outils de longue durée doivent exposer une tâche durable ou un résultat final que l'appelant peut inspecter ; garder les contrats destinés aux machines stables et séparés du texte localisé ; les descriptions doivent expliquer quand, pourquoi et comment un outil interagit avec les autres.
## Garder l’état de l’UI sur son thread et respecter le comportement natif de la plateforme
- Conserver l'état observable de l'interface sur le thread d'interface utilisateur, et laisser les résultats issus de l'arrière-plan franchir la frontière sous forme de valeurs immuables.
- Limiter l'observation à la plus petite vue ayant besoin d'une valeur ; un état à haute fréquence comme la progression, le survol ou la lecture ne doit pas invalider des arbres de vues sans rapport.
- Ne pas démarrer d'effets de bord persistants depuis le corps du rendu ; utiliser des tâches ou des contrôleurs conscients du cycle de vie, avec une annulation et un démontage explicites, et garder le corps du rendu rapide et sans effet de bord.
- Préserver le comportement natif de la plateforme pour le focus, le clavier, la touche Échap, les menus, la restauration de fenêtre, l'annulation, l'état de glisser-déposer et la confirmation de fermeture.
- Les contrats de délégué et de gestionnaire d'achèvement doivent se terminer exactement une fois, sur chaque chemin de succès, d'échec, d'annulation et de cible manquante.
- Ne pas supposer qu'un framework ou un callback d'achèvement arrive sur le thread d'interface utilisateur, sauf si l'API le garantit.
## Traiter les médias sans bloquer et garder exactes les valeurs de temps et d’image
- Utiliser le chargement asynchrone des propriétés pour les ressources externes, et ne pas appeler d'accesseurs synchrones obsolètes susceptibles de bloquer le thread appelant.
- Conserver les valeurs dans leur domaine exact, comme des entiers précis de temps, d'image ou d'unité, aussi longtemps que possible, et ne convertir en virgule flottante qu'aux frontières explicites avec l'interface utilisateur ou un format externe.
- Définir et préserver la sémantique de la source, comme le timing, l'échelle, les transformations, la couleur, l'alpha, l'agencement des canaux et les métadonnées, sauf si la fonctionnalité les modifie explicitement.
- Réutiliser les lecteurs, contextes de rendu et pipelines lorsque leur cycle de vie le permet, et ne pas les reconstruire pendant une interaction continue, sauf si une invalidation l'exige.
- Limiter le nombre de décodeurs, lecteurs, exports, inférences et extractions concurrents, et propager l'annulation à travers chaque boucle de décodage, de rendu et d'export, en vérifiant entre les segments lorsqu'une étape sous-jacente ne peut pas être annulée.
- Tester avec des pistes manquantes, des conteneurs inhabituels, une durée nulle ou indéfinie, des entrées pivotées ou avec alpha, des débits non standard, et l'annulation.
## Utiliser des jetons de design au lieu de valeurs de style codées en dur
- Styliser l'ensemble via le système partagé de jetons de design ; ne jamais coder en dur les espacements, tailles, graisses de police, rayons, largeurs de bordure, opacités, couleurs, ombres ou durées d'animation.
- Si un jeton nécessaire n'existe pas, l'ajouter d'abord au système de jetons plutôt que de coder une valeur en dur.
- La recherche de ressources doit fonctionner dans l'application packagée, en exécution locale et dans les tests ; utiliser l'abstraction partagée de recherche de ressources plutôt qu'un sondage spécifique à une fonctionnalité, et traiter chaque agencement de bundle comme une configuration distincte à vérifier.
- Ne pas utiliser des indicateurs réservés au débogage pour sélectionner un chemin de ressource ou un comportement fondamentalement différent, sauf si cette différence est intentionnelle et testée.
- Lors de l'ajout d'une ressource au bundle, mettre à jour le manifeste du package, les scripts de build et les tests, vérifier l'agencement final du package, et veiller à ce que le code protégé par un indicateur de fonctionnalité optionnel puisse être compilé avec et sans cet indicateur.
## Donner à chaque opération une fin visible et des journaux utiles
- Chaque opération initiée par l'utilisateur ou par l'automatisation doit atteindre un état observable de succès, d'échec, de refus ou d'annulation.
- Ne pas ignorer une erreur à une frontière d'interaction lorsque l'échec change le résultat ; la convertir en état d'interface, en erreur d'outil, en échec de tâche ou en journal exploitable.
- Le travail « tire et oublie » (fire-and-forget) n'est autorisé que lorsqu'il est explicitement au mieux-effort ; il ne doit pas porter la persistance requise, et ses échecs doivent pouvoir être ignorés sans risque ou être enregistrés.
- Journaliser les échecs avec l'opération concernée, des identifiants stables et la phase du cycle de vie ; ne jamais écrire de secrets, d'identifiants de connexion ou de saisies utilisateur brutes dans les journaux.
- Conserver suffisamment de diagnostics pour distinguer l'annulation, l'échec de validation, la ressource indisponible, l'échec du framework et la violation d'un invariant interne.
- Utiliser les assertions uniquement pour les invariants du programmeur, et non pour les saisies utilisateur récupérables, les fichiers externes, les compétitions de cycle de vie ou les requêtes d'automatisation.
## Écrire de petits tests déterministes qui peuvent tourner en parallèle
- Les tests unitaires sont concis et centrés sur le comportement : un test établit un seul invariant ou une seule régression, avec uniquement la mise en place nécessaire pour le rendre clair, en utilisant des noms descriptifs et sans commentaires qui narrent les étapes.
- Utiliser des tests paramétrés pour les matrices de cas limites et des constructeurs de fixtures partagés, plutôt que de dupliquer la mise en place entre les suites.
- Tester en passant par la plus petite interface stable qui prouve le comportement, et éviter tout couplage avec des détails privés ou des nombres d'appels accessoires.
- Chaque correction de bug doit être accompagnée d'un test de régression qui échoue pour le défaut d'origine lorsque c'est possible, et qui couvre les chemins négatifs : validation, absence d'effet, échec partiel, annulation, achèvement obsolète et nettoyage.
- Les tests de concurrence se coordonnent de façon déterministe à l'aide de verrous, de continuations ou de points d'ancrage injectés, sans reposer sur des temporisations, la chance de timing ou l'ordre d'ordonnancement.
- Les tests s'exécutent en parallèle : ne pas partager d'état global mutable, de noms temporaires fixes, de ports ou de répertoires ; utiliser des répertoires temporaires uniques et les nettoyer, et exclure des tests unitaires les identifiants de connexion réels, les services externes et les fichiers utilisateur réels.
## Revoir d’abord la correction et indiquer exactement quels tests ont tourné
- Examiner la correction avant le style : retracer le comportement modifié à travers les appelants, l'état partagé, le travail en arrière-plan, la persistance, l'annulation, l'interface utilisateur et les surfaces d'automatisation.
- Prioriser les constats qui causent une perte de données, une corruption, des plantages, des blocages, des compétitions, une exposition de sécurité, des résultats incorrects, une annulation cassée ou un succès trompeur, puis venir à la performance, la maintenabilité et la finition.
- Rechercher le blocage du thread d'interface utilisateur, les erreurs de concurrence comme un état validé après une suspension sans revalidation, les tâches sans propriétaire, l'annulation manquante, une diffusion non bornée, des verrous déséquilibrés et des callbacks repris deux fois, ainsi que la logique de domaine dupliquée, les caches non sûrs et l'arithmétique de cas limites non validée ; limiter la revue au changement et à ce qu'il affecte directement.
- Vérifier les affirmations : exécuter le build et les tests appropriés au changement, éprouver les chemins d'annulation et de cycle de vie pour les changements de concurrence, et mesurer les changements de performance sous une charge de travail représentative.
- Rendre compte exactement de ce qui a été exécuté et de ce qui ne l'a pas été ; ne jamais prétendre à une vérification complète du build, des tests ou de l'exécution lorsque seule une partie a été effectuée.
- Garder des commits ciblés, avec un préfixe de catégorie précis et exact et sans nettoyage sans rapport, et décrire un changement par son résumé, son approche, ses décisions de conception clés et les preuves de test, plutôt que par la séquence des modifications effectuées.
