


Modifier un pipeline de données à plugins sur deux runtimes sans le casser
Pour un agent de codage dans un backend à plugins sur deux runtimes. Il garde les versions dans un fichier, applique les filtres de test à la bonne tâche, suit les règles de plugins, de concurrence et de couches de service, et vérifie que chaque nouveau test échoue sans sa modification.
Pour utiliser ces règles d'IA
## Garder les versions dans un seul fichier et lancer le build depuis la même racine
Gardez les versions et les coordonnées de la chaîne d'outils comme référence canonique, et compilez toujours depuis le même emplacement.
- Stockez chaque numéro de version et chaque coordonnée de chaîne d'outils dans un seul fichier canonique ; tous les consommateurs (scripts de build, utilitaires, tests, descripteurs de paquets) le lisent, et vous ne mettez à jour que ce fichier.
- Lorsqu'une valeur est intentionnellement codée en dur ou dupliquée à plusieurs endroits, comme un segment de chemin lié à la compatibilité du runtime, ne la traitez pas comme un bug et ne la calculez pas dynamiquement ; retrouvez la valeur actuelle à l'endroit canonique et alignez-vous dessus.
- Exécutez toutes les commandes de build et de test depuis une racine cohérente afin que les résolutions de chemins relatifs restent identiques à chaque fois.
- Préférez une chaîne d'outils vendorisée et figée, fournie par une étape de bootstrap, plutôt qu'un langage ou un gestionnaire de versions installé globalement, afin que chaque environnement compile de la même manière.
## Appliquer les filtres de test à la tâche qui contient vraiment les tests
Comprenez comment les tâches de test sont reliées entre elles avant d'exécuter un sous-ensemble filtré.
- Une tâche de test parapluie exclut souvent toutes les classes elle-même et se contente de déléguer à des sous-tâches ; un filtre par nom appliqué à cette tâche ne correspondra silencieusement à rien et donnera l'illusion d'avoir réussi. Appliquez les filtres à la tâche concrète qui possède réellement les tests.
- Les suites sont scindées volontairement : certains tests sont déplacés hors de leur suite évidente, par exemple les tests qui démarrent un second runtime s'exécutent dans la suite qui fournit ce runtime. Si un filtre ne trouve rien, la cible se trouve probablement dans l'autre suite.
- Sachez où se trouvent respectivement les tests unitaires, d'intégration et d'acceptation afin d'invoquer le bon point d'entrée.
## Restructurer le code pour qu'un nouveau cas semble prévu dès le départ
Lorsque vous modifiez du code, ne vous contentez pas d'ajouter à l'existant. Évaluez si le code environnant garde du sens avec votre changement. Si une abstraction a été conçue pour un seul cas et que vous en ajoutez un second, restructurez-la de sorte que le résultat se lise comme si les deux cas avaient été prévus dès le départ. Donnez à chaque concept distinct sa propre unité clairement nommée, et composez ces unités dans un code de plus haut niveau afin que l'intention soit évidente sans avoir à lire chaque implémentation.
## Construire les plugins sur les classes de base et le registre du framework
Construisez les extensions sur les points d'extension propres au framework, pas à côté.
- Héritez de la classe de base spécifique au type et surchargez ses hooks de cycle de vie : un hook de configuration ou d'enregistrement, plus le point d'entrée propre au type.
- Déclarez la configuration via le DSL d'options déclaratif du framework plutôt que de lire des paramètres bruts, afin que la validation, la coercition et la génération de documentation soient prises en charge automatiquement.
- Définissez la concurrence au niveau de la classe : partagée lorsque plusieurs threads de travail peuvent appeler l'instance en même temps, unique lorsque les appels doivent être sérialisés pour qu'un seul thread s'exécute à la fois.
- Faites passer toute recherche de plugin par le registre central, enregistrez-y les nouveaux plugins intégrés, et laissez les plugins externes être découverts en tant qu'unités empaquetées.
## Placer les métriques et la logique commune dans un délégateur qui enveloppe chaque plugin
Confinez les préoccupations transversales dans une couche d'enveloppe et récupérez les collaborateurs auprès du framework.
- Exécutez chaque plugin derrière un délégateur qui ajoute les préoccupations transversales telles que les métriques et la sécurité des threads ; lorsque vous modifiez un comportement, déterminez s'il relève du délégateur ou du plugin lui-même.
- Faites suivre aux métriques une hiérarchie d'espaces de noms, en créant les enfants via un appel d'espace de noms.
- Fournissez une métrique objet-nul qui implémente la même interface mais rejette les données, pour les cas où les métriques sont désactivées ou en contexte de test.
- N'instanciez jamais directement les objets de métriques ; recevez-les du framework. Les mixins standards comme la journalisation et la configuration sont inclus automatiquement, tandis que les fonctionnalités plus lourdes restent optionnelles.
## Garder la boucle des workers rapide et démarrer puis arrêter dans le bon ordre
Protégez le chemin critique et respectez l'ordre de démarrage et d'arrêt.
- Considérez la boucle de travail comme le chemin critique : elle récupère des lots, les fait passer dans le pipeline compilé, et signale leur achèvement. N'y ajoutez ni appels bloquants ni allocations lourdes.
- Respectez l'ordre strict du cycle de vie : les sorties et les filtres s'enregistrent en premier, puis les workers démarrent, puis les entrées démarrent en dernier ; à l'arrêt, les entrées s'arrêtent en premier, les workers se vident, puis les filtres et les sorties se ferment. Violer cet ordre entraîne une perte de données ou un blocage.
- Utilisez des primitives atomiques pour l'état partagé entre threads, et non de simples champs booléens, car la visibilité mémoire inter-threads n'est sinon pas garantie.
## Compiler la configuration du pipeline en code et l'exporter pour le débogage
Certains moteurs compilent la configuration déclarative en classes exécutables au runtime via un pipeline en plusieurs étapes : analyser le texte de configuration avec une grammaire pour produire un arbre syntaxique ; abaisser cet arbre vers une représentation intermédiaire, un graphe orienté acyclique de nœuds et de conditions ; générer le code source de chaque nœud et le compiler en classes au runtime dans un espace de noms dédié ; puis relier les unités compilées à la boucle d'exécution. Les conditions se compilent en fonctions de prédicat reliées aux nœuds de branchement. Fournissez un indicateur de débogage qui écrit le code source généré dans un répertoire afin de pouvoir inspecter ce qui s'exécute réellement.
## Traiter de la même façon les erreurs, les valeurs nulles et la journalisation dans tout le code
Appliquez des conventions cohérentes de structure, de gestion des erreurs, des valeurs nulles et de journalisation dans toute la base de code.
- Organisez les modules par domaine fonctionnel, et non par couche architecturale, et placez chaque nouveau type dans le domaine auquel il appartient.
- Gardez un nommage cohérent : suffixes et préfixes réservés pour les types d'extension, de délégateur et abstraits ; marquez les classes comme finales sauf si l'héritage est prévu.
- Préférez les exceptions non vérifiées à l'exécution ; n'utilisez les exceptions vérifiées que lorsque c'est inévitable, toujours avec un message descriptif et une chaîne de cause préservée.
- Pour les valeurs nulles, validez les arguments du constructeur avec une vérification explicite de non-nullité, annotez les contrats intentionnellement nullables, et utilisez un type optionnel pour les recherches susceptibles d'échouer plutôt que comme remplacement général du null.
- Déclarez un seul logger statique et final par classe, et choisissez les niveaux délibérément : diagnostics fins en niveau bas, problèmes récupérables en avertissements, échecs non capturés en erreurs.
- Privilégiez les champs finaux immuables, utilisez des types de map concurrents pour les caches partagés, et documentez la sécurité des threads lorsqu'un type est partagé.
## Faire passer les réponses de l'API par un seul utilitaire et mettre la logique dans des classes de commande
Stratifiez le service de sorte que le routage, les réponses et la logique aient chacun un emplacement unique.
- Faites hériter les modules d'API d'une base commune qui étend la classe de base du framework web et reçoit les dépendances communes via son constructeur.
- Montez les points de terminaison sur des préfixes de chemin déclarés dans une carte d'espaces de noms centrale unique ; ajoutez-y les nouveaux espaces de noms, plutôt que via un routage ad hoc.
- Faites passer chaque réponse par un unique utilitaire de réponse qui enveloppe les charges utiles avec des métadonnées par défaut ; n'écrivez pas de réponses brutes à la main.
- Représentez les erreurs avec un petit ensemble de sous-classes d'erreurs typées telles que non-trouvé, requête-invalide et délai-dépassé ; levez-les depuis les gestionnaires et laissez la base formater une enveloppe cohérente unique.
- Conservez la logique métier dans des classes de commande dédiées, pas dans les gestionnaires de routes ; un gestionnaire construit une commande et transmet son résultat à l'utilitaire de réponse.
## Relier deux runtimes et tester l'aller-retour à travers les deux
Lorsque du code fait le pont entre deux runtimes, par exemple une machine virtuelle hôte pilotant un second langage, enregistrez les classes pontées au point d'enregistrement désigné par le framework et suivez les patterns déjà en place. Récupérez les objets de contexte propres à chaque thread au moment de leur utilisation, sans jamais les stocker à la construction, car un contexte stocké échoue lorsqu'il est réutilisé depuis un autre thread. Testez l'aller-retour à travers les deux couches plutôt que chaque côté séparément, afin de détecter les bugs de coercition de type et d'enregistrement que des tests unitaires monocouches manqueraient. Les tests qui chargent le second runtime doivent faire pointer leurs chemins de recherche de modules vers l'emplacement vendorisé ; ce chemin est intentionnel, ne le modifiez donc pas en éditant ces tests.
## Tester avec les utilitaires du vrai pipeline et nettoyer l'état ensuite
Exercez un comportement réel à l'aide d'utilitaires conçus à cet effet, et ne laissez aucun état résiduel.
- Préférez les utilitaires de pipeline fournis, qui démarrent un pipeline réel et capturent les résultats, plutôt que de construire manuellement des objets de pipeline.
- Pour l'isolation, définissez des classes de plugin jetables directement dans le fichier de spécification et enregistrez-les auprès du registre réel.
- Utilisez des exemples partagés pour les contrats d'interface et un contexte partagé pour la configuration commune ; pilotez les tests d'intégration avec un pattern fixture-et-service qui s'amorce à partir de fichiers de fixtures.
- Vérifiez que les blocs de récupération ou de capture existants couvrent réellement les nouveaux chemins de code ajoutés.
- Nettoyez l'état global ou celui de la machine virtuelle lors du teardown : chaque propriété que vous définissez doit avoir une réinitialisation correspondante afin que l'état ne fuite pas vers d'autres tests.
- Préférez des doublures concrètes, conçues sur mesure, qui héritent de vraies classes de base, plutôt que des mocks génériques ; réservez les mocks aux frontières d'infrastructure, et laissez la méthode réelle s'exécuter lorsqu'un stub ne sert qu'à la configuration.
## Vérifier que chaque nouveau test échoue sans sa modification
Assurez-vous que chaque test dépend réellement du changement qu'il protège.
- Vérifiez que chaque nouveau test échoue sans le changement correspondant ; un test qui passe avec ou sans le correctif ne prouve rien.
- Vérifiez le comportement spécifique introduit par le changement, et non des conditions préexistantes : ancrez l'assertion à une différence observable, telle qu'un nouveau champ, une valeur modifiée, ou une ligne de log auparavant absente.
- Pour un correctif de bug, reproduisez d'abord le bug dans le test ; observez-le échouer pour la même raison que le bug se manifeste, puis appliquez le correctif et observez-le réussir.
- Méfiez-vous des mocks tautologiques : simuler une méthode pour qu'elle retourne la valeur que vous vérifiez ensuite est circulaire, et si supprimer l'implémentation tout en gardant uniquement le mock fait toujours passer le test, ce dernier n'exerce que le mock.
## Introduire les changements incompatibles par étapes et masquer les secrets dans les logs
Modifiez le comportement selon une progression par étapes, protégez les valeurs sensibles, et gardez la documentation synchronisée.
- Échelonnez les changements incompatibles : introduisez le nouveau comportement en option activable et ajoutez des avertissements de dépréciation dans les versions mineures, puis changez la valeur par défaut tout en conservant une option de désactivation, et enfin supprimez l'ancien comportement dans les versions majeures.
- Pour la configuration portant des valeurs sensibles, appliquez le type ou l'enveloppe dédiée aux secrets du framework et surchargez la représentation en chaîne pour que la valeur soit masquée ; n'écrivez jamais de telles valeurs dans les logs, à quelque niveau que ce soit, par défaut.
- Considérez la documentation comme faisant partie du changement : mettez à jour la documentation de référence et pour contributeurs concernée à chaque changement de comportement, respectez les conventions de chaque format, et exigez la documentation avant de fusionner des fonctionnalités visibles par l'utilisateur.
