Aperçu de la structure

Quand une solution correcte est mal conçue

Une leçon sur l’IA, les nombres aléatoires et MOSAICO : de RandomNumero à RandomRange, entre correction, réutilisation et responsabilité.

Articles /quand-une-solution-correcte-est-mal-concue
Quand une solution correcte est mal conçue

12 min

Salvatore Mosaico · 2026

Sommaire

Introduction

Un programme peut toujours fournir le bon résultat tout en étant mal conçu. Cette leçon met en scène un dialogue entre enseignant, élève et intelligence artificielle. La tâche semble simple : utiliser Math.random() de JavaScript pour générer des entiers aléatoires. Le véritable objectif apparaît à la fin.

Le premier jour, nous construisons une fonction générale donnant un entier de 1 à N. Le lendemain, nous l’étendons à un intervalle [a,b]. L’IA fournit une formule fonctionnelle. L’enseignant surprend la classe : les mathématiques sont correctes, mais la solution viole un choix architectural du projet.

Il faut distinguer quatre jugements : validité syntaxique, correction mathématique, réussite des tests et cohérence de conception. Lorsqu’une capacité a déjà été isolée et testée à un niveau inférieur, le niveau supérieur doit l’utiliser plutôt que reproduire sa logique, si tel est le contrat du système.

Le bon résultat ne suffit pas. Un bon programmeur place aussi chaque responsabilité au bon endroit. Les dialogues suivants sont des simulations pédagogiques, non des transcriptions d’une conversation réelle.

1. Premier jour : de [0,1) à [1,N]

L’enseignant présente Math.random(). Elle renvoie un nombre pseudo-aléatoire supérieur ou égal à zéro et inférieur à un : 0 ≤ Math.random() < 1. Dans [0,1), le crochet inclut zéro et la parenthèse exclut un. Supposons N entier positif, petit comme dans ces exemples scolaires.

Scène de classe : comprendre l’intervalle

Enseignant : Nous voulons un entier de 1 à N. Que se passe-t-il si nous multiplions le résultat par N ?

Élève : [0,1) devient [0,N).

Enseignant : Dans le modèle mathématique, nous pouvons approcher N aussi près que voulu, sans jamais l’atteindre.

Élève : Math.floor donne 0, 1, 2, …, N−1.

Enseignant : Comment obtenir 1, 2, …, N ?

Élève : Il suffit d’ajouter 1.

function RandomNumero(N) {
    return Math.floor(Math.random() * N) + 1;
}

On multiplie par N, on arrondit vers le bas, puis on ajoute 1. Avec N=6, on simule un dé. Dans le modèle uniforme idéal, chaque résultat correspond à un sous-intervalle de longueur 1/6. L’implémentation réelle est pseudo-aléatoire et approximativement uniforme : cela ne prouve pas un hasard parfait.

2. Deuxième jour : un intervalle quelconque

Élève : Comment générer un entier entre a et b, extrémités comprises ?

Enseignant : Combien d’entiers contient [a,b] ?

Élève : Je dirais b−a.

Enseignant : Vérifions [5,10] : 5, 6, 7, 8, 9, 10. Six valeurs, mais 10−5 vaut cinq.

Élève : C’est donc b−a+1, car on compte les deux extrémités.

Pour [5,10], il faut six résultats possibles. RandomNumero(10−5+1) donne un entier de 1 à 6. Pour obtenir 5, 6, 7, 8, 9, 10, nous décalons toutes les valeurs de quatre : nous ajoutons a−1.

3. La réponse de l’intelligence artificielle

Interrogée directement dans notre simulation, l’IA propose une formule connue :

function RandomRange(a, b) {
    return Math.floor(Math.random() * (b - a + 1)) + a;
}

L’explication mathématique est correcte : Math.random() donne une valeur dans [0,1) ; multiplier par b−a+1 donne [0,b−a+1) ; Math.floor donne les entiers de 0 à b−a ; ajouter a donne les entiers de a à b.

Les extrémités conviennent aussi : zéro donne a, une valeur suffisamment proche de un donne b. Les formules décrivent la même transformation mathématique. Deux appels séparés ne doivent toutefois pas forcément produire le même nombre : ils réalisent des tirages différents.

Le rebondissement

IA : La fonction demandée est Math.floor(Math.random() * (b−a+1)) + a.

Élève : La formule est correcte. Nous avons terminé.

Enseignant : Non. Par rapport à notre conception convenue, il y a une erreur de conception.

Élève : Mais les résultats sont corrects. Où est l’erreur ?

Un défaut important peut-il exister lorsque le programme renvoie les bonnes valeurs ?

4. Où l’IA s’est-elle trompée ?

Afficher la réponse et l’explication

La formule n’est pas fausse. Le problème est de réécrire la génération aléatoire en ignorant RandomNumero(N), déjà construite et testée. Math.floor(Math.random() * …) réapparaît : le savoir de bas niveau est dupliqué.

RandomRange devient un second endroit connaissant Math.random(). La version cohérente avec le projet délègue à RandomNumero(b−a+1), puis ajoute a−1.

Enseignant : L’IA a-t-elle résolu le problème reçu ?

Élève : Oui, la fonction donne un nombre entre a et b.

Enseignant : A-t-elle respecté le projet existant ?

Élève : Non. Elle a résolu à nouveau ce que RandomNumero savait déjà faire.

Enseignant : Exactement : dans cette scène, elle a traité la demande locale sans exploiter l’histoire et l’architecture du système.

Le défaut apparaît en examinant dépendances, maintenance et évolution, non en exécutant une seule fois la fonction. Ce n’est pas une incapacité inévitable de l’IA : le contexte et une contrainte explicite de réutilisation peuvent orienter une autre réponse. Vérifier le respect de cette contrainte reste nécessaire.

5. Pourquoi la duplication devient dangereuse

Supposons que nous voulions journaliser les tirages, remplacer la source pendant les tests ou adopter un générateur adapté à des exigences de sécurité. Modifions la fonction de base :

function RandomNumero(N) {
    const resultat = Math.floor(Math.random() * N) + 1;
    console.log("Nombre généré :", resultat);
    return resultat;
}

La version de RandomRange utilisant RandomNumero bénéficie automatiquement de la journalisation. Celle appelant directement Math.random() suit un autre chemin et ne journalise rien. Le message concerne la valeur intermédiaire de 1 à N : enregistrer le résultat final de a à b exige un choix explicite au niveau supérieur.

Conséquences architecturales

  • Duplication : la même décision technique apparaît à plusieurs endroits.
  • Maintenance : il faut penser à répéter chaque modification dans toutes les implémentations.
  • Incohérence : certaines fonctions peuvent suivre le nouveau comportement et d’autres conserver l’ancien.
  • Testabilité : remplacer la source aléatoire à un endroit unique pour des tests répétables devient plus difficile.
  • Responsabilité : le niveau supérieur connaît un détail attribué au niveau de base.

La duplication ne concerne pas seulement des caractères : elle concerne une décision. Quand celle-ci change, chaque copie devient une erreur potentielle. La réutilisation aide tant que la fonction de base préserve son contrat ; un changement incompatible impose de revoir les appelants.

6. La lecture selon MOSAICO

MOSAICO construit le logiciel par niveaux. Les fonctions de base encapsulent des capacités élémentaires vérifiées ; les niveaux supérieurs les composent sans reproduire leurs mécanismes internes.

  • Base : RandomNumero(N) produit un entier aléatoire de 1 à N.
  • Niveau supérieur : RandomRange(a,b) compte les valeurs de [a,b] et décale le résultat.
  • Frontière : dans cet exemple, seule RandomNumero connaît Math.random et Math.floor.
  • Avantage : les changements internes compatibles avec le contrat se propagent aux appelants.

La formule directe franchit cette frontière. MOSAICO rend visible la hiérarchie : RandomRange → RandomNumero → Math.random.

Une question est : « Quelle formule donne le plus directement un entier entre a et b ? » Le concepteur demande : « Quelle capacité existe déjà dans le système et comment la composer pour obtenir le nouveau comportement ? » Cette deuxième question transforme une collection de fonctions en architecture.

7. Une précision importante

Dans un programme isolé sans RandomNumero, la formule directe convient aux exemples proposés. Aucun interdit universel ne frappe Math.random(). L’erreur vient du contexte : nous avions délibérément construit un niveau de base pour le réutiliser.

Bien concevoir ne signifie pas ajouter partout des fonctions intermédiaires. Il faut attribuer clairement une responsabilité et ne pas la dupliquer lorsqu’elle a été centralisée.

Nous pouvons vérifier que a et b sont entiers et que a≤b :

function RandomRange(a, b) {
    if (!Number.isInteger(a) || !Number.isInteger(b)) {
        throw new Error("a et b doivent être entiers");
    }
    if (a > b) {
        throw new Error("a ne doit pas dépasser b");
    }
    return RandomNumero(b - a + 1) + a - 1;
}

La relation entre a et b relève de la fonction d’intervalle. La fonction de base doit garantir son propre contrat sur N, y compris lorsqu’elle est appelée directement. C’est une version pédagogique, non une bibliothèque générale pour toutes les valeurs Number.

Note technique : limites et sécurité

Number.isInteger ne garantit pas un entier sûr. Une bibliothèque réelle doit définir son domaine, vérifier aussi la largeur b−a+1 et tenir compte des arrondis en virgule flottante. Vérifier seulement des entiers sûrs ne prouve pas une distribution exactement uniforme sur des intervalles immenses. Nos exemples utilisent [1,6], [5,10] et [−3,3].

Math.random n’est pas cryptographiquement sûr : ces exemples ne doivent pas générer de mots de passe, de jetons ou de secrets. Centraliser le générateur facilite son remplacement, mais ne sécurise pas automatiquement la transformation de ses résultats.

Conclusion : la nouvelle compétence du programmeur

Une demande locale peut recevoir une réponse localement correcte. Le système possède pourtant une histoire : fonctions disponibles, responsabilités attribuées, dépendances autorisées et décisions à centraliser.

Le programmeur doit se demander si la solution appartient au projet. Cela exige mémoire architecturale, connaissance des conséquences et refus d’accepter automatiquement le code parce qu’il fonctionne.

La différence est entre « Je sais calculer directement le résultat » et « Le système sait déjà faire l’essentiel : je dois le réutiliser et ajouter seulement ce qui manque ».

Le défaut n’était pas dans le nombre renvoyé, mais dans l’ignorance d’un savoir déjà organisé. Le passage de RandomNumero(N) à RandomRange(a,b) est petit, mais illustre MOSAICO : réutiliser le code, les responsabilités et les décisions.

Références techniques

À lire aussi

Le programmeur à l’époque de l’intelligence artificielle