Concevoir des modules réutilisables
Un programme Vitte en plusieurs fichiers a besoin de chemins cohérents et d’une interface claire. Cette leçon suit un appel entre deux modules, puis transforme cet exemple en méthode d’organisation.
Chemin logique et fichier source
Une déclaration space nomme le module auquel appartient un fichier. Dans le projet d’exemple, les sources se trouvent sous examples/tutorials/modules/ et leurs chemins commencent par tutorials::modules. Le chemin relie le code à son rôle dans l’arborescence ; adoptez une convention unique et appliquez-la à tous les fichiers du projet.
Le module producteur peut exposer une procédure :
space tutorials::modules::calcul;
export proc additionner(gauche: i64, droite: i64) -> i64 {
return gauche + droite;
}
export indique que la procédure fait partie de l’interface publique. Ses paramètres et son type de retour forment un contrat pour le code appelant. La règle de calcul reste dans le module qui la possède.
Importer et appeler le module
Le fichier principal importe le chemin sous un alias, puis utilise le nom qualifié :
space tutorials::modules::main;
use tutorials::modules::calcul as calcul;
proc main() -> i64 {
if calcul::additionner(20, 22) == 42 {
return 0;
}
return 1;
}
calcul::additionner garde la provenance du nom visible. Si un autre module fournit aussi une procédure appelée additionner, le chemin indique laquelle est appelée. L’alias peut raccourcir un long chemin tout en conservant cette qualification.
Résoudre un module et exécuter le programme
La résolution doit savoir où chercher les fichiers importés. Dans l’exemple du dépôt, VITTE_MODULE_PATH=examples indique la racine appropriée :
VITTE_MODULE_PATH=examples build/bin/vitte run examples/tutorials/modules/main.vit --quiet --no-color
Résultat confirmé : code de sortie 0. Les deux sources vérifiées sont main.vit et calcul.vit. Si l’import échoue, vérifiez dans cet ordre : le chemin de module déclaré, la racine de recherche, le chemin importé, puis l’export du nom utilisé.
Concevoir une interface Vitte
Un export utile répond à un besoin réel d’un autre module, porte un nom qui décrit un comportement stable et présente des paramètres et un résultat qui rendent les obligations de l’appelant compréhensibles. Une interface bien séparée permet de changer l’implémentation sans modifier ses appels.
Un module qui exporte toutes ses fonctions internes rend ses choix difficiles à faire évoluer. Commencez avec l’interface la plus petite utile. Ajoutez un export quand un besoin concret apparaît, pas seulement parce que la déclaration existe.
Un import de module n’équivaut pas à un accès universel à ses déclarations. L’export du producteur et la résolution de l’appelant doivent tous deux convenir. Les imports globaux et certains réexports ont des limites dans les chemins de compilation ; privilégiez les imports explicites déjà exercés par le dépôt.
Faire grandir l’exemple sans perdre le fil
Pour ajouter une opération, commencez par écrire sa signature et son résultat attendu. Ajoutez ensuite la procédure exportée dans calcul.vit, puis un appel qualifié dans main.vit. Vérifiez les deux fichiers ensemble avant d’ajouter un troisième module. Cette progression permet d’attribuer plus facilement un diagnostic au chemin, à la visibilité, à la signature ou au corps.
Quand un nom de module change, recherchez ses imports et actualisez le chemin dans les sources et la configuration. Un chemin importé fait partie de la structure du programme : gardez-le cohérent plutôt que de corriger les appels un par un au hasard.
Une arborescence d’apprentissage
Pour un petit projet, commencez par deux fichiers comme dans l’exemple vérifié :
examples/
└── tutorials/
└── modules/
├── main.vit
└── calcul.vit
Le module logique de chaque fichier correspond à son chemin d’import. Dans un projet plus grand, regroupez les sources selon les responsabilités réelles : calcul, validation, modèles. Le nom de répertoire n’est pas une documentation suffisante ; le chemin space et les imports doivent rester cohérents avec l’organisation voulue par la CLI.
Déboguer par couche
Un import qui ne fonctionne pas peut échouer à plusieurs endroits. Si la source n’est pas trouvée, examinez la racine de recherche et le chemin du module. Si la source est trouvée mais que le nom appelé est inconnu, vérifiez le nom de la déclaration et son export. Si le nom est trouvé mais que l’appel échoue, comparez le nombre, l’ordre et les types des arguments avec la signature.
La modification d’une seule de ces couches à la fois facilite le diagnostic. Le chemin tutorials::modules::calcul, l’alias local calcul et le nom exporté additionner ont trois rôles différents ; leur évolution séparée révèle lequel intervient dans le diagnostic.
Réduire les dépendances visibles
Un module devient plus facile à réutiliser lorsqu’il dépend de peu d’autres modules et expose des opérations plutôt que des détails internes. Pour chaque import, demandez-vous si la dépendance est nécessaire dans tout le fichier ou seulement dans une procédure. La grammaire autorise plusieurs formes d’import, mais les noms explicites rendent la provenance plus simple à vérifier.
Quand deux modules doivent partager un même type, placez-le dans le module qui en possède le sens ou dans un module de modèle explicitement choisi. Évitez que les deux modules s’importent mutuellement uniquement pour contourner une frontière mal dessinée.
Préparer une revue de changement d’API
Avant de modifier une procédure exportée, la liste de ses appelants permet d’établir l’étendue du changement. Vérifiez la compatibilité de la signature, la visibilité des types qui y apparaissent et les tests qui décrivent son comportement. Si vous renommez un module, adaptez aussi la racine de résolution si elle dépend de sa nouvelle place.
Pour l’exemple officiel, la commande complète définit VITTE_MODULE_PATH=examples, lance examples/tutorials/modules/main.vit et utilise l’alias calcul. Un échec « module introuvable » conduit d’abord à vérifier la racine et le chemin ; un échec « déclaration privée » conduit à vérifier export.
Un graphe de dépendances doit avoir un sens
Chaque import établit une relation entre un module consommateur et un module fournisseur. À mesure que le programme s’agrandit, ces relations forment un graphe. Un découpage efficace fait dépendre le code de haut niveau d’interfaces stables, tandis que les modules élémentaires ne dépendent pas des couches qui les utilisent. Une dépendance circulaire brouille le propriétaire d’un type et complique l’ordre dans lequel les interfaces sont comprises.
Les responsabilités donnent un critère de séparation : calcul, validation, modèle métier et point d’entrée ne sont pas nécessairement le même rôle. Une procédure doit se trouver dans le module qui possède sa règle. Un type partagé peut appartenir à un module de modèle, mais sa création ne justifie pas automatiquement un module supplémentaire si aucun autre code ne profite de cette frontière.
La recherche des modules dépend du contexte de lancement
L’exemple vérifié emploie deux fichiers sous examples/tutorials/modules, avec des espaces de noms tutorials::modules::main et tutorials::modules::calcul. La variable VITTE_MODULE_PATH=examples fournit à la commande la racine de recherche. Le résultat réussi dépend donc de l’assemblage du chemin logique, des fichiers réels, de l’import et de la configuration de la CLI.
Cette configuration fait partie de la reproductibilité du programme. Déplacer les fichiers sans mettre à jour leur espace de noms ou changer la racine sans revoir les chemins importés peut casser la résolution avant que la procédure soit analysée. Une commande copiée depuis un autre répertoire peut également pointer sur un fichier inexistant. Les diagnostics de modules se comprennent mieux lorsque la commande complète est examinée avec l’arborescence réelle.
Une API publique engage le module dans le temps
Une procédure exportée devient un point de contact pour ses appelants. Modifier son nom, son ordre de paramètres ou son résultat peut rendre l’API incompatible. Ajouter une contrainte ou exposer un type privé peut aussi bloquer la compilation des consommateurs. Le module gagne à exporter les opérations dont il garantit le sens, tout en gardant privés les détails qui peuvent évoluer.
La qualification calcul::additionner montre au lecteur d’où vient l’appel. L’alias raccourcit le chemin sans effacer entièrement la provenance. L’exemple vérifié établit la résolution et l’export pour deux modules dans un chemin précis ; il ne garantit pas les formes d’import ou de réexport qui ne figurent pas dans cette fixture. Une interface est à la fois une surface d’usage et un engagement de compatibilité.
Organisation des tests et de la documentation par module
Les tests d’un module décrivent les comportements que son interface promet. Des cas proches du calcul peuvent rester auprès du module de calcul ; les cas qui valident la commande complète appartiennent plutôt à une suite d’intégration. La séparation permet de repérer si un défaut vient d’une règle locale ou de la résolution entre plusieurs fichiers.
La documentation publique devrait suivre les déclarations exportées et mentionner les erreurs, les unités et les limites d’utilisation. Un commentaire sur un détail privé peut évoluer avec le corps ; une garantie publique engage les appelants. Cette différence aide à choisir où placer les explications et quelles informations doivent rester stables.
Évolution de l’arborescence
Déplacer un fichier peut modifier son chemin logique, les imports, la racine de recherche et les commandes utilisées par les tests. L’arborescence physique est donc une partie de l’outillage, même lorsque les noms de modules sont explicités dans le source. Les changements de disposition sont plus faciles à comprendre lorsqu’ils sont accompagnés de la liste des chemins de module touchés.
Le compilateur peut également imposer une racine ou une convention de résolution différente selon la commande. La fixture actuelle montre une configuration avec VITTE_MODULE_PATH=examples. Une application distribuée doit documenter son propre point de lancement, car le répertoire de travail d’un utilisateur n’est pas nécessairement celui du dépôt de développement.