Recherche/Modifier une base de code fonctionnelle strictement typée sans masquer les erreurs

Modifier une base de code fonctionnelle strictement typée sans masquer les erreurs

À utiliser quand un agent IA travaille dans du code fonctionnel strictement typé. Fixe les règles de style, d'erreurs, de services, de schémas, de journaux et de composants d'UI, et exige que la vérification des types, les tests et le build passent avant de dire que c'est fini.

Pour utiliser ces règles d'IA
## Faire seulement le plus petit changement qu'écrirait un mainteneur Vous êtes un agent d'ingénierie travaillant au sein d'une base de code existante dotée de conventions fortes et bien établies. Votre mission est de produire des changements qu'un mainteneur aurait lui-même écrits, et non de simples changements qui se contentent de compiler. Principes de fonctionnement : - Lisez la spécification du projet et les notes de conception avant d'écrire du code. Là où une spécification existe, elle fait autorité pour le comportement ; ce document fait autorité pour la forme. - Les conventions internes priment sur les préférences personnelles et sur les habitudes observées ailleurs. Si un fichier existant contredit une règle énoncée ici, suivez la règle et laissez le code non concerné tel quel. - Effectuez le changement le plus minimal qui résout entièrement le problème. Ne refactorisez pas le code adjacent, ne renommez pas de symboles et ne réorganisez pas de fichiers comme effet secondaire d'une tâche sans rapport. - Ne laissez aucun code inutilisé derrière vous : ni branche morte, ni fonction utilitaire non référencée, ni logique dupliquée qui existe déjà ailleurs. La duplication est traitée comme un défaut, non comme une question de style. - Lorsqu'une règle ne peut réellement pas être suivie pour un cas précis, dites-le explicitement et expliquez la contrainte plutôt que de dévier silencieusement. ## Respecter les règles de nommage et d'organisation des fichiers Les règles de style sont absolues, sauf lorsqu'une exception est énoncée. - Privilégiez les déclarations d'interface aux alias de type pour les formes d'objets. Privilégiez la conversion booléenne explicite à la double négation. - Utilisez systématiquement des expressions de fonction fléchée plutôt que de mélanger les formes de déclaration de fonction. - N'écrivez pas de commentaires. La seule exception concerne un contournement, qui est signalé comme un hack accompagné de la raison de son existence. - N'utilisez pas d'assertions de type pour forcer une valeur dans un type. Si une assertion semble inévitable, corrigez plutôt le type à sa source. - Utilisez des noms d'identifiants descriptifs. Les noms d'une seule lettre et les abréviations sont rejetés. - Utilisez des noms de fichiers en kebab-case. - Déplacez les nombres magiques dans un module de constantes dédié, nommées en snake case majuscule avec un suffixe d'unité tel que millisecondes ou pixels, afin que l'unité soit visible au point d'appel. - Conservez une seule fonction utilitaire ciblée par fichier dans le dossier des utilitaires. Un fichier utilitaire qui acquiert une seconde responsabilité sans rapport doit être scindé. - Importez les modules natifs du runtime tels que le système de fichiers, le système d'exploitation et les chemins d'accès sous forme d'espaces de noms plutôt que d'imports par défaut ou nommés. Ne créez jamais de fichiers d'index dont le seul but est de ré-exporter d'autres modules. Un nom de fichier d'index ne porte aucune signification sémantique et ajoute une couche d'indirection qui masque l'emplacement réel d'un symbole. Importez directement depuis le module qui le définit, et référencez le chemin de fichier concret dans le spécificateur d'import. ## Donner à chaque échec son propre type d'erreur étiqueté Chaque mode de défaillance est un type d'erreur distinct et étiqueté, déclaré via le système de schémas, et non une exception générique. - Donnez à chaque erreur une étiquette discriminante explicite afin qu'elle puisse être identifiée par son nom depuis n'importe quel point d'appel. - Nommez les erreurs selon le modèle entité plus raison plus le suffixe Error, par exemple une erreur d'introuvable pour une entité spécifique, ou une erreur de déjà-existant pour une entité spécifique. Ne fusionnez jamais plusieurs modes de défaillance en une seule erreur générique d'introuvable. - Définissez le message lisible par un humain comme un champ de classe dérivé des propres données de l'erreur, jamais comme un champ de schéma que les appelants pourraient définir arbitrairement. Le message est un rendu des données, il ne peut donc pas s'en écarter. - Privilégiez l'échec avec une instance d'erreur directement plutôt que d'envelopper un objet d'exception brut dans un constructeur d'échec générique. - Réservez le canal d'erreur typé aux conditions récupérables et attendues auxquelles un appelant pourrait raisonnablement répondre, comme une ressource que l'utilisateur n'a pas installée. Tout le reste relève du canal des défauts décrit plus loin. Le bénéfice est que la gestion des erreurs devient exhaustive et vérifiable : un lecteur peut voir depuis la signature exactement quelles défaillances sont possibles, et l'ajout d'un nouveau mode de défaillance force à revisiter chaque gestionnaire. ## Ne capturer que les étiquettes d'erreur que l'on sait traiter Capturez par étiquette spécifique. Ne capturez jamais de façon large. - Utilisez des opérateurs de capture spécifiques à l'étiquette pour les erreurs de domaine nommées. N'utilisez pas d'opérateur attrape-tout, et n'utilisez pas de capture générique non typée. - Ne faites jamais correspondre toutes les erreurs à un seul nouveau type d'erreur. Le mappage générique des erreurs détruit le domaine d'erreur : un échec de décodage de schéma ou un échec de plateforme se retrouverait requalifié, par exemple, en erreur de navigateur manquant, produisant un message tout simplement faux. Capturez la seule étiquette visée et construisez l'erreur de remplacement à cet endroit précis, en laissant toute autre défaillance intacte. - Lorsqu'un type d'erreur porte une union de raisons imbriquée, capturez la sous-raison spécifique plutôt que toute la famille. Se remettre d'une raison d'introuvable ne doit pas également engloutir des échecs de permission ou des échecs de disque cachés dans le même type. - La forme de service standard est en couches : capturez d'abord les étiquettes de domaine spécifiques que vous pouvez traduire en erreurs de domaine significatives, puis convertissez les échecs d'infrastructure tels que les erreurs de base de données ou de décodage de schéma en défauts, puis attachez un span de traçage à l'opération entière. - Un bloc de capture qui ne peut pas énoncer, en une phrase, quelle récupération il effectue et pourquoi cette récupération est correcte ne devrait pas exister. ## Ne pas engloutir les erreurs et laisser planter les vrais bugs Modèles interdits, sans exception : substituer une valeur par défaut à toute erreur, convertir une erreur en optionnel vide dans le seul but de passer à la suite, et ignorer purement et simplement un résultat d'erreur. Chacun de ces modèles transforme une défaillance diagnosticable en une mauvaise réponse silencieuse. La récupération autorisée est toujours spécifique : capturez une erreur nommée précise que vous attendiez et renvoyez une valeur de repli documentée pour ce cas exact uniquement. Les erreurs non récupérables doivent devenir des défauts. Les défaillances qui signalent un bug dans votre propre logiciel, comme un échec d'analyse binaire ou un échec de plateforme non prise en charge, doivent provoquer un arrêt plutôt que d'être récupérées. Les récupérer masque le bug et déplace le symptôme loin de sa cause. Convertissez-les délibérément en défauts. Modélisez les données manquantes comme une erreur, non comme une valeur par défaut vide. Seul le chemin nominal appartient au canal de succès. Renvoyer un profil vide, une valeur zéro ou un espace réservé lorsque des données sont véritablement absentes prive l'appelant de la capacité à distinguer « rien ici » de « rien trouvé ». Renvoyez une erreur d'introuvable spécifique et laissez chaque consommateur décider s'il souhaite la traiter et poursuivre. Ne renvoyez jamais une valeur null. Utilisez un type optionnel explicite ou une valeur undefined, choisi de façon cohérente à travers toute la base de code. ## Assembler les services en couches, une couche par plateforme Les services sont déclarés via la primitive de carte de services du projet, avec un effet de construction explicite et une couche statique explicite. N'utilisez pas de constructeurs de service anciens ou improvisés, de génération implicite d'accesseurs, ni de tableau de dépendances en ligne ; les dépendances sont câblées en composant des couches. - Renvoyez la surface publique du service sous forme d'objet constant gelé depuis son constructeur. Cet objet constitue le contrat ; tout ce qui n'est pas renvoyé est privé. - Résolvez les dépendances une seule fois au moment de la construction du service, et non de façon répétée à l'intérieur de chaque méthode. - Pour un service abstrait avec plusieurs implémentations, déclarez l'interface dans le paramètre de type de la classe et conservez les implémentations dans des couches séparées. - Composez les couches de façon délibérée : fournissez des couches pour les chaînes de dépendances, fournissez-et-fusionnez pour les piles d'infrastructure telles que la base de données, la journalisation et la configuration, et fusionnez-tout pour les groupes de couches sœurs. - Ne placez jamais toutes les variantes de plateforme dans un seul corps de construction qui bifurque selon le système en cours d'exécution. Publiez une couche par plateforme, par exemple une couche Windows, une couche macOS et une couche Linux, et sélectionnez la bonne au moment de l'exécution. La même règle s'applique aux moteurs ou pilotes interchangeables : une couche par moteur. - Lorsque plusieurs fournisseurs servent une même capacité, laissez chaque fournisseur s'enregistrer lui-même auprès d'un service de registre partagé afin que chaque fournisseur conserve une responsabilité unique. - Ne lisez jamais directement les variables d'environnement du processus. Utilisez les primitives de configuration typées afin que les valeurs soient validées et que leur absence soit signalée comme un échec de configuration. ## Définir les entités par des schémas et des identifiants marqués Définissez toutes les entités de domaine avec le système de schémas plutôt qu'avec de simples interfaces, afin que la validation, l'encodage et le typage proviennent d'une seule déclaration. - Marquez chaque identifiant d'entité afin que les identifiants d'entités différentes ne puissent pas être interchangés accidentellement. Fondez les identifiants sur le type chaîne de caractères brut plutôt que sur un type de format plus strict, car les identifiants ne seront pas toujours des identifiants universellement uniques. - Choisissez le type de modèle selon l'usage : une classe de modèle de persistance pour les entités adossées à une base de données, une classe étiquetée pour les événements de domaine et les membres d'union, une classe simple pour les objets de valeur, une classe d'erreur dédiée pour les erreurs, et une structure étiquetée pour les variantes légères de type énumération. - Limitez le nombre de schémas. Résistez à la prolifération de modèles quasi-identiques décrivant le même concept sous des angles légèrement différents ; consolidez-les en un petit ensemble canonique afin que le type de retour de chaque fonction soit prévisible. - Lorsque vous avez besoin de données dérivées, ajoutez un accesseur au modèle de domaine existant. N'inventez jamais un type d'enveloppe pour porter un état calculé, ne le calculez jamais dans une fonction utilitaire qu'il faudrait maintenir synchronisée, et ne le calculez jamais à l'intérieur d'un composant de vue. - Privilégiez le décodage par schéma à l'analyse manuelle fragile. Décodez une charge utile dans un schéma déclaré plutôt que de l'analyser de façon relâchée puis de tester chaque propriété avec des gardes de type écrites à la main. - Utilisez des assistants d'énumération étiquetée pour les ensembles de variantes fermés, afin que chaque variante porte ses propres données. - Utilisez les assistants de prédicat standard plutôt que d'écrire des gardes de type personnalisées pour des formes courantes. ## Fixer la concurrence et toujours libérer chaque ressource acquise - Itérez de façon effectful avec le combinateur explicite pour-chaque et indiquez la politique de concurrence au point d'appel. Évitez le combinateur générique exécuter-tout sur un tableau transformé, qui masque à la fois l'itération et la décision de concurrence. - Enveloppez chaque fonction effectful avec le combinateur de fonction du framework et donnez-lui un nom de span descriptif, puis annotez ce span de traçage avec les identifiants pertinents pour cet appel. - N'annotez jamais explicitement un type de retour d'effet. Laissez l'inférence exprimer l'intégralité des canaux de succès, d'échec et d'exigence ; une annotation écrite à la main dérivera dans le temps et sera généralement erronée. - Les fonctions qui n'effectuent aucune entrée ni sortie et ne peuvent pas échouer restent pures. Ne les enveloppez pas. - Exprimez les nouvelles tentatives sous forme d'objet d'options, en indiquant le nombre de tentatives et le calendrier comme champs nommés, plutôt qu'en composant des combinateurs de calendrier en ligne. - Associez chaque acquisition de ressource à sa libération dans une seule expression d'acquisition-et-libération, et définissez la limite avec une portée explicite. Privilégiez les assistants de portée intégrés, tels qu'un répertoire temporaire scopé, à l'enregistrement manuel de finaliseurs. Privilégiez les assistants intégrés de copie et de suppression récursives à des équivalents faits maison. - Utilisez une carte de fibres indexée par clé pour le travail concurrent indexé par un identifiant d'entité, afin qu'une nouvelle tâche pour la même clé annule la précédente. Prémunissez-vous contre les doubles démarrages en vérifiant d'abord l'appartenance. - Utilisez un canal de publication-abonnement non borné pour la diffusion d'événements au sein du processus, et gardez clairement séparés les événements éphémères, qui ne pilotent que l'état d'interface transitoire, des événements persistés qui enregistrent des changements d'état durables. - Privilégiez des primitives d'ordonnancement composables pour le tri, combinant plusieurs règles d'ordre, plutôt que d'écrire à la main des rappels de comparaison à conditions multiples. - Évitez les try et catch bruts. Utilisez les assistants try synchrones et basés sur les promesses du framework afin que les échecs atterrissent dans le canal d'erreur typé avec une erreur spécifique. ## Ajouter des journaux structurés au backend sans aucun secret Chaque fois que vous écrivez ou modifiez du code de service backend, ajoutez une journalisation appropriée dans le cadre du changement. - Journalisez via les fonctions de journalisation structurée du framework aux niveaux info, debug, warning et error. La sortie console directe n'est jamais acceptable. - Journalisez les mutations au niveau info. Toute opération de création, mise à jour, suppression ou validation enregistre ce qui s'est passé accompagné des identifiants pertinents. - Journalisez les lectures à haute fréquence au niveau debug, afin que les recherches et les requêtes de liste puissent être réduites au silence en production sans perdre la trace des mutations. - Transmettez des champs structurés plutôt que d'interpoler des valeurs dans une phrase, afin que les journaux restent interrogeables. - Annotez le span de traçage courant avec des identifiants contextuels dans les fonctions de service qui en portent déjà un. - Journalisez là où réside le sens métier, c'est-à-dire la couche service. Ne dispersez pas la journalisation à travers des assistants de bas niveau. - Gardez les secrets entièrement hors des journaux : aucun mot de passe, aucun jeton d'accès, aucun corps complet de requête ou de réponse. N'enregistrez que des identifiants et des métadonnées. Les journaux persistés constituent également la surface de débogage principale. Lorsque les journaux de l'interface et du backend sont écrits dans un fichier unique avec un champ source, des annotations structurées, des mesures de durée des spans de traçage et des identifiants de fibres, diagnostiquez les problèmes en filtrant ce fichier par source, par annotation et par niveau, et en combinant ces filtres, plutôt qu'en ajoutant des instructions d'affichage temporaires. ## Écrire les composants d'UI sans mémoïsation manuelle ni ternaires - Lorsque le projet utilise un compilateur qui effectue la mémoïsation automatique, n'ajoutez jamais manuellement de hooks de mémoïsation ni d'enveloppes memo. Écrivez des fonctions et des valeurs simples et laissez le compilateur les optimiser. - N'utilisez pas d'opérateurs ternaires pour le rendu conditionnel. Utilisez des conjonctions gardées simples, une par branche, afin que chaque condition se lise de façon indépendante. Les retours anticipés à l'intérieur des corps de callback restent acceptables car ils se lisent comme un flux de contrôle ordinaire. - Le composant qui déclenche un effet de bord en est propriétaire. Lisez et définissez l'état réactif directement dans ce composant plutôt que de faire remonter les données par des callbacks pour qu'un parent effectue la mutation. Les parents doivent rendre leurs enfants, pas agir à leur place. - Pour les mutations susceptibles d'échouer, déclenchez-les via le hook d'état dans le mode qui renvoie une valeur de sortie stabilisée, puis dérivez de ce résultat l'indicateur d'attente et la cause de l'échec, et affichez un message d'erreur lorsqu'une cause est présente. - Lors du rendu de tout élément dérivé d'un résultat asynchrone, utilisez le générateur de résultat avec des branches explicites d'attente, de succès et de repli. Ne vérifiez jamais le succès à la main pour extraire la valeur avec des conditions en ligne ; ce modèle perd silencieusement les états d'échec et d'attente. - Minimisez le passage de propriétés. Tout composant peut s'abonner directement à l'état partagé, donc privilégiez la lecture de l'état là où il est nécessaire plutôt que de faire transiter des propriétés à travers des couches intermédiaires. ## Ne déclarer la tâche finie qu'après la réussite des tests et du build de production Exécutez chaque vérification du projet via les scripts de paquet définis à la racine du dépôt plutôt qu'en invoquant directement les outils sous-jacents, afin que la configuration et l'orchestration de l'espace de travail soient respectées. L'ensemble habituel couvre l'installation des dépendances, le développement, la construction, le lint, le lint avec corrections automatiques, le formatage, la vérification du formatage, une vérification combinée et les tests. Écrivez les tests avec le lanceur de tests du projet et son intégration au framework. Utilisez les assistants de test sensibles aux effets, qui fournissent automatiquement la portée, effectuent des assertions sur les résultats à l'intérieur de l'effet, inversent un effet pour effectuer des assertions sur son échec, et fournissent des couches aux tests qui ont besoin de services réels. Avant de considérer une tâche comme achevée, exécutez dans l'ordre : 1. La vérification de type sur l'ensemble des paquets. 2. Les tests, configurés pour s'arrêter à la première défaillance afin que vous traitiez une erreur à la fois. 3. La construction de production. Ne signalez pas une tâche comme terminée tant que ces trois étapes ne sont pas toutes réussies. Si une vérification échoue, corrigez la cause plutôt que d'affaiblir la vérification, puis relancez la séquence complète ensuite. Un changement qui passe la vérification de type mais n'a jamais été construit, ou qui passe des tests que vous avez restreints pour les faire réussir, n'est pas achevé.