Convertisseur d'unités

Prev Next

Pour télécharger le convertisseur d’unités : Masse, rendez-vous sur : [tulip](https://<span translate=).co/library/apps/unit-converter--mass">Library

Pour télécharger le convertisseur d'unités : Volume, rendez-vous sur : [tulip.co/library/apps/unit-converter--volume">Library](https://<span translate=)

Dans cet article, Tamás, l’un de nos développeurs de la bibliothèque, présente les choix techniques qui ont présidé au développement de l’application « Convertisseur d’unités » disponible dans la bibliothèque.

Pourquoi j’ai utilisé une liste d’objets plutôt qu’un tableau

Bonjour, je m’appelle Tamás et je travaille chez Tulip.

Lorsque j’ai créé ce composant de conversion d’unités, l’une de mes premières décisions architecturales a été de ne pas utiliser de tableau. J’ai plutôt choisi de tout stocker dans des listes d’objets initialisées en tant que variables par défaut du composant.

Le raisonnement était simple : dans ce contexte, la conversion d’unités se comporte comme une constante mathématique, et non comme des données opérationnelles. Les relations sont purement multiplicatives, changent rarement et ne nécessitent ni édition, ni agrégation, ni filtrage, ni requêtes relationnelles lors de l’exécution. Introduire un tableau aurait impliqué : * la création et la maintenance d’une infrastructure supplémentaire, * la configuration de requêtes et d’ aggregations, * l’ajout d’une couche de dépendance supplémentaire à chaque application utilisant le composant.

Cela aurait accru la complexité sans améliorer les capacités.

J’ai donc préféré intégrer les jeux de données directement au sein du composant :* Jeux de données de base canoniques : * _lengthToBaseDefaults * _massToBaseDefaults * _volumeToBaseDefaults

  • Dictionnaires de synonymes :
    • _lengthUnitAliases
    • _massUnitAliases
    • _volumeUnitAliases

Ils sont tous deux définis comme valeurs par défaut des variables. Cela permet au composant d’être :* Portable* Autonome* Déterministe* Facile à copier d’une application à l’autre

Si l'ensemble de données se comporte comme une constante, je préfère le protéger à l'intérieur du composant plutôt que de l'externaliser.

Pourquoi pas un tableau ?

Une solution basée sur un tableau introduirait une dépendance structurelle que ce problème ne justifie pas.

Les définitions d’unités ne sont pas des données transactionnelles. Ce sont des définitions structurelles. Les traiter comme des enregistrements opérationnels serait disproportionné par rapport au problème.

Mon objectif ici était la proportionnalité architecturale : n’utiliser que l’infrastructure strictement nécessaire pour résoudre le problème.

Principe de conception : logique protégée, configuration flexible

Ce composant repose sur deux idées fondamentales :1. La normalisation canonique de base 2. La tolérance des entrées grâce à la résolution des synonymes

J’ai délibérément séparé la vérité mathématique de la flexibilité de l’utilisateur.* Les ensembles de données de base canoniques définissent les relations mathématiques.* Les dictionnaires de synonymes définissent la manière dont la flexibilité des entrées utilisateur est interprétée.

Ces deux couches font partie intégrante de la conception — ce ne sont pas des ajouts de dernière minute.

Dans le modèle de base canonique, chaque unité est mise en correspondance avec une seule unité de référence au sein de son domaine. Chaque conversion passe par cette référence. Cela crée un chemin de normalisation unique et garantit que toutes les unités partagent le même ancrage mathématique.

Unités de base par domaine

J’ai explicitement défini l’unité de base par domaine :* Longueur → m* Masse → kg* Volume → L

Ces choix ont été délibérés :* Il s’agit de normes industrielles.* Elles minimisent les écarts d’arrondi.* Elles permettent au système de rester intuitif pour les ingénieurs.

Clés d’unités canoniques (unités prises en charge)

Tous les mappages d’alias doivent aboutir à l’une de ces clés canoniques.

  • Masse (unité de base : kg) µg, mg, g, kg, t, lb, oz
  • Longueur (unité de base : m) mm, cm, m, km, in, ft, yd, mi
  • Volume (base : L) ml, cl, µl, mm³, cm³, l, m³, fl_oz_us, pt_us, qt_us, gal_us

Chaque domaine dispose de sa propre liste d’objets de données de base. Chaque entrée définit une unité et son facteur de conversion vers la base (factorToBase).

La conversion suit toujours le même schéma :* Entrée → Unité de base* Unité de base → Unité cible

Pas de branchement. Pas de gestion des paires.

Interface interne protégée

Afin d’éviter toute modification accidentelle de la logique, j’ai isolé les variables principales :* _inputValue* _inputUnit* _targetUnit* _conversionResult

Le moteur central ne dépend que de celles-ci.

Il est entouré de deux couches de configuration légères :* 01 – CONFIGURATION UTILISATEUR : ÉTAPE D’ENTRÉE Mappe les sources externes vers les variables internes.* 03 – CONFIGURATION UTILISATEUR : ÉTAPE DE SORTIE Transmet le résultat calculé vers l’extérieur.

Cela permet de maintenir la stabilité du cœur de calcul tout en garantissant une intégration flexible.

Décomposition du déclencheur – Comment fonctionne MAGIC

Le déclencheur MAGIC protégé effectue deux opérations logiques :1. Résolution des synonymes d’unités2. Application de la normalisation canonique de base

Couche de résolution des alias

Chaque domaine dispose d’un dictionnaire de synonymes, par exemple :* _lengthUnitAliases

Chaque objet contient : * brut * canonique

Exemple :{ "brut" : "mm", "canonique" : "mm" }{ "brut" : "millimètre", "canonique" : "mm" }{ "brut" : "m³", "canonique" : "m3" }Cela permet de faire correspondre plusieurs formats d’écriture à une seule clé canonique. Il est important de noter que je n’ai pas dupliqué les entrées au sein du jeu de données de base. La couche de synonymes existe uniquement pour absorber la variabilité des entrées sans multiplier les définitions mathématiques.

Formule de base canonique

Une fois les unités canoniques déterminées, la formule est la suivante :résultat = valeurEntrée × facteurVersBase(entréeCanonique) ÷ facteurVersBase(cibleCanonique)

Exemple de déclencheur (domaine de la longueur)

(@Variable._inputValue * array_value_at_index( map_to_number_list(@Variable._lengthToBaseDefaults , 'factorToBase'),      array_index_of( map_to_text_list(@Variable._lengthToBaseDefaults , 'unit'), array_value_at_index( map_to_text_list(@Variable._lengthUnitAliases , 'canonical'),          array_index_of( map_to_text_list(@Variable._lengthUnitAliases , 'raw'), @Variable._inputUnit ) ) )    ))/array_value_at_index( map_to_number_list(@Variable._lengthToBaseDefaults , 'factorToBase'), array_index_of( map_to_text_list(@Variable._lengthToBaseDefaults , 'unit'),    array_value_at_index( map_to_text_list(@Variable._lengthUnitAliases , 'canonical'),      array_index_of( map_to_text_list(@Variable._lengthUnitAliases , 'raw'), @Variable._targetUnit ) ) ))D'un point de vue structurel, il s'agit d'une logique en couches :* Conversion de « raw » en « canonical »* Conversion de « canonical » en « factor »* Normalisation via la base

Cela peut paraître dense, mais cette structure en ligne est intentionnelle afin de garantir l’autonomie du moteur et d’éliminer tout état intermédiaire.

Pourquoi cette structure a-t-elle été choisie ?

Je souhaitais trois garanties :* Pas d’explosion des paires* Pas de dérive d’arrondi due à des chemins multiples* Pas de réécriture des déclencheurs lors de l’extension des unités

Le modèle de base canonique m’offre ces trois garanties. Lorsque j’ajoute une nouvelle unité, j’ajoute exactement une entrée factorToBase. Le moteur ne change pas. Lorsque je prends en charge un nouveau format d’écriture, j’ajoute exactement une entrée d’alias. Les calculs ne changent pas. Cette séparation est au cœur de la décision architecturale.

Réutilisation multi-domaines : même moteur, différents ensembles de données

Autre choix délibéré : la logique du moteur est indépendante du domaine. La longueur, la masse et le volume partagent le même déclencheur structurel. Seule la variable de jeu de données change. Au lieu de créer trois convertisseurs différents, j’ai construit un moteur canonique unique et laissé le jeu de données définir le domaine. Cela réduit la charge cognitive et garantit la prévisibilité du comportement des composants.

Pourquoi cela est évolutif

L’évolutivité s’opère au niveau des ensembles de données. L’ajout d’unités ne nécessite pas : * de modifier les expressions de déclenchement, * d’ajouter des branches conditionnelles, * de dupliquer la logique.

L’algorithme reste stable. C’est le jeu de données qui évolue.

Extension du composant

Ajout d’une nouvelle unité canonique

  • Étendez le jeu de données _ToBaseDefaults concerné.
  • Ajoutez l'unité et factorToBase.
  • Ne modifiez pas le déclencheur.

Ajouter un nouveau format d'écriture

  • Étendez le dictionnaire _UnitAliases correspondant.
  • Ajoutez « raw » et la clé canonique correspondante.
  • Ne modifiez pas le jeu de données de base.

C'était intentionnel : je souhaitais offrir une flexibilité conviviale sans multiplier les définitions mathématiques.

Gestion des échecs (modèle de recherche sécurisé)

Il existe deux points de défaillance : 1. L'unité brute n'est pas trouvée dans le dictionnaire d'alias ; 2. L'unité canonique n'est pas trouvée dans le jeu de données de base

Si l’une ou l’autre de ces recherches échoue, le calcul n’est pas exécuté.

Cela garantit : * Aucune valeur obsolète * Aucune erreur de calcul silencieuse * Un comportement déterministe

Pourquoi ne pas utiliser des tableaux distincts pour les unités et les facteurs ?

Le stockage des unités et des facteurs dans des tableaux distincts créerait un risque de dépendance positionnelle. Si l’ordre d’une liste change et pas celui de l’autre, le système cesse de fonctionner sans signaler d’erreur. En stockant ensemble les unités et les facteursToBase au sein d’objets structurés, j’ai éliminé cette fragilité structurelle.

Philosophie fondamentale

Ce composant est intentionnellement simple dans sa structure et rigoureux dans son comportement.* Les calculs sont centralisés.* Le moteur est protégé.* La flexibilité s’exerce en périphérie.

Je l’ai conçu pour que la logique reste stable au fil du temps tandis que l’ensemble des unités prises en charge peut évoluer en toute sécurité. Le moteur ne change pas. L’ensemble de données s’enrichit.

Participez

Rejoignez la communauté pour partager des améliorations, proposer des unités supplémentaires, signaler des problèmes ou discuter des choix architecturaux