Pourquoi la robustesse est indispensable
Un algorithme « fonctionne » souvent sur un exemple simple, puis échoue dès qu’une entrée est manquante, qu’une liste est vide, qu’un nombre dépasse une borne, ou qu’un arrondi change un résultat. La robustesse consiste à prévoir ces situations et à rendre le comportement de l’algorithme stable, explicable et testable. L’objectif n’est pas de tout compliquer, mais d’ajouter des garde-fous : valider les entrées, définir des valeurs par défaut, contrôler les bornes, et écrire des règles de calcul non ambiguës.
Validation des entrées : refuser tôt, expliquer clairement
Principe
Avant de calculer, vérifier que les données respectent les règles minimales (type, format, intervalle, cohérence). Si une règle est violée, arrêter le traitement ou demander une correction, plutôt que de produire un résultat faux.
Étapes pratiques
- Définir les contraintes : ex. « montant ≥ 0 », « catégorie dans une liste autorisée », « date au format attendu ».
- Normaliser : supprimer espaces, convertir en nombre, uniformiser la casse si nécessaire.
- Valider : si une contrainte échoue, retourner une erreur explicite (ou un statut) au lieu de continuer.
- Tracer (optionnel) : conserver un message d’erreur ou une liste d’erreurs pour aider au diagnostic.
Exemple de règles de validation (budget)
- Montant : numérique, non négatif, raisonnable (ex. ≤ 1 000 000).
- Catégorie : non vide, fait partie des catégories connues (ou créer automatiquement une catégorie « Autre » selon la règle choisie).
- Mois : entier 1..12, année positive.
Gestion des valeurs manquantes : décider d’une politique
Cas typiques
- Champ absent (ex. catégorie non fournie).
- Valeur vide (ex. "").
- Valeur spéciale (ex. null, None, N/A).
Politiques possibles (à choisir et documenter)
- Refuser : l’entrée est invalide, on demande une correction.
- Remplacer par défaut : ex. catégorie = « Autre », montant = 0 (attention : peut masquer une erreur).
- Ignorer l’élément : ex. ignorer une ligne de dépense incomplète, mais compter et signaler le nombre d’éléments ignorés.
Une bonne pratique est de rendre la politique visible dans les sorties : « 2 entrées ignorées car incomplètes ».
Listes vides : éviter les divisions par zéro et les accès hors limites
Risques fréquents
- Calculer une moyenne sur une liste vide (division par zéro).
- Accéder au premier élément d’une liste vide.
- Supposer qu’un regroupement contient au moins une catégorie.
Parades
- Tester la taille avant d’accéder : si taille = 0, retourner un résultat neutre (ex. total = 0) ou un message (« aucune donnée »).
- Définir des valeurs neutres : somme = 0, liste résultat = [], dictionnaire résultat = {}.
- Pour une moyenne : décider « moyenne = 0 » ou « moyenne indisponible » (préférable si cela a du sens métier).
Bornes et limites : contrôler les extrêmes
Exemples
- Budget mensuel négatif ou trop grand.
- Quantité d’articles = 0, ou quantité énorme.
- Pourcentage de dépassement : division par zéro si budget = 0.
Technique : clamp et règles explicites
Le clamp consiste à forcer une valeur dans un intervalle : si x < min alors x = min, si x > max alors x = max. À utiliser seulement si c’est cohérent (sinon, refuser l’entrée). Toujours préciser la règle : « on refuse » ou « on ajuste ».
Erreurs d’arrondi : travailler en unités sûres
Problème
Les nombres décimaux peuvent produire des résultats inattendus (ex. 0,1 + 0,2 ≠ 0,3 selon la représentation). Pour un budget, ces écarts peuvent déclencher de fausses alertes de dépassement.
Continuez dans notre application.
Vous pouvez écouter le livre audio écran éteint, recevoir un certificat gratuit pour ce cours et accéder également à 5 000 autres cours en ligne gratuits.
Ou poursuivez votre lecture ci-dessous...Téléchargez l'application
Bonnes pratiques
- Travailler en centimes (entiers) : 12,34 € devient 1234.
- Arrondir à des moments contrôlés : arrondir à l’affichage, pas à chaque opération.
- Comparer avec une tolérance si vous restez en décimal : ex. considérer égal si |a-b| < 0,01.
Ordre des opérations : rendre les calculs non ambigus
Risque
Une formule peut être interprétée différemment si les parenthèses ne sont pas explicites. Exemple : alerte = total - budget / budget peut être compris comme total - (budget/budget) au lieu de (total-budget)/budget.
Règles
- Ajouter des parenthèses même si « ce n’est pas nécessaire » : la lisibilité réduit les erreurs.
- Découper une formule en variables intermédiaires nommées :
depassement = total - budget, puistaux = depassement / budget.
Variables non initialisées : initialiser systématiquement
Symptômes
- Une variable n’a pas de valeur si une branche n’est pas exécutée.
- Un accumulateur n’est pas remis à zéro entre deux calculs.
Parades
- Initialiser les accumulateurs avant la boucle : total = 0, alertes = [].
- Initialiser les structures de regroupement : dictionnaire des catégories vide.
- Définir une valeur par défaut pour les résultats : statut = "OK".
Duplication de logique : réduire les divergences
Problème
Copier-coller une règle (ex. validation d’un montant) à plusieurs endroits conduit à des incohérences : une version est corrigée, l’autre non.
Stratégies
- Centraliser les règles dans une fonction ou un bloc unique de pseudo-code réutilisable (ex.
validerMontant). - Créer une checklist commune (préconditions, invariants, sorties) utilisée pour chaque exercice.
- Nommer les constantes (ex.
MONTANT_MAX) au lieu de répéter des nombres « magiques ».
Méthode de test : scénarios normal, limite, invalide
Pour rendre un algorithme testable, préparer des scénarios structurés :
- Cas normal : données réalistes, attendues.
- Cas limite : valeurs aux bornes (0, liste vide, budget = 0, quantité = 1, montant très grand).
- Cas invalide : type incorrect, valeur négative, catégorie inconnue si non autorisée, champs manquants.
Chaque scénario doit préciser : entrées, résultat attendu, et pourquoi c’est un test important.
Exercice 29 — Simulateur de budget mensuel
Énoncé
Construire un simulateur qui prend un budget mensuel et une liste de dépenses (montant, catégorie). Il doit produire : total dépensé, reste, répartition par catégorie, alertes de dépassement (global et/ou par catégorie si des plafonds existent), et un résumé. Les entrées peuvent être variables : catégories manquantes, montants au format texte, liste vide.
Décisions de robustesse (à fixer avant le pseudo-code)
- Monnaie : travailler en centimes (entiers).
- Dépense incomplète : si montant manquant ou non numérique → entrée invalide (on ignore la dépense et on la signale).
- Catégorie manquante : catégorie = « Autre » (politique de remplacement).
- Budget = 0 : autoriser, mais l’alerte de taux (%) doit être « non calculable » (éviter division par zéro).
Scénarios de test
| Type | Entrées | Attendu |
|---|---|---|
| Cas normal | Budget=2000,00 ; Dépenses: (50,00 “Transport”), (120,30 “Courses”), (15,00 “Loisirs”) | Total=185,30 ; Reste=1814,70 ; Répartition par catégorie correcte ; aucune alerte |
| Cas limite | Budget=0,00 ; Dépenses: (0,00 “Autre”) | Total=0 ; Reste=0 ; pas d’erreur ; alerte dépassement=non ; taux dépassement non calculé |
| Cas limite | Budget=100,00 ; Dépenses: liste vide | Total=0 ; Reste=100 ; répartition vide ; message “aucune dépense” ou équivalent |
| Cas invalide | Budget=-10,00 ; Dépenses quelconques | Refus : erreur “budget doit être ≥ 0” |
| Cas invalide | Dépense: montant="abc" catégorie="Courses" | Dépense ignorée + compteur d’erreurs=1 ; total inchangé |
| Cas limite | Budget=100,00 ; Dépenses: (100,00 “Courses”) | Total=100 ; Reste=0 ; pas d’alerte si règle “dépassement strictement > budget” |
| Cas limite | Budget=100,00 ; Dépenses: (100,01 “Courses”) | Total=100,01 ; Reste=-0,01 ; alerte dépassement global |
Pseudo-code final commenté
// Simulateur de budget mensuel (robuste et testable) // Entrées : budgetTexte, listeDepenses (chaque dépense : montantTexte, categorieTexte) // Sorties : resume (total, reste, repartition, alertes, erreurs) CONSTANTE MONTANT_MAX_CENTIMES = 100000000 // 1 000 000,00 FONCTION parseCentimes(texte): // Normalise et convertit "12,34" ou "12.34" en 1234 SI texte est manquant OU texte vide: RETOURNER (ECHEC, "montant manquant") t = texte trim t = remplacer ',' par '.' dans t SI t n'est pas un nombre: RETOURNER (ECHEC, "montant non numérique") valeur = convertir t en décimal centimes = arrondir(valeur * 100) // Contrôle de bornes SI centimes < 0: RETOURNER (ECHEC, "montant négatif") SI centimes > MONTANT_MAX_CENTIMES: RETOURNER (ECHEC, "montant trop grand") RETOURNER (OK, centimes) FONCTION categorieNormalisee(catTexte): SI catTexte est manquant OU catTexte trim est vide: RETOURNER "Autre" RETOURNER catTexte trim FONCTION simulerBudget(budgetTexte, listeDepenses): erreurs = [] // liste de messages total = 0 // en centimes repartition = dictionnaire vide (categorie -> centimes) // 1) Valider budget (statutB, budget) = parseCentimes(budgetTexte) SI statutB = ECHEC: RETOURNER erreur fatale "Budget invalide: " + message // budget peut être 0, c'est autorisé // 2) Traiter les dépenses (robuste aux entrées invalides) POUR chaque depense D dans listeDepenses: (statutM, montant) = parseCentimes(D.montantTexte) SI statutM = ECHEC: ajouter "Dépense ignorée: " + message à erreurs CONTINUER categorie = categorieNormalisee(D.categorieTexte) total = total + montant SI categorie n'existe pas dans repartition: repartition[categorie] = 0 repartition[categorie] = repartition[categorie] + montant // 3) Calculer reste et alertes reste = budget - total alertes = [] SI total > budget: ajouter "Dépassement du budget" à alertes // Taux de dépassement (éviter division par zéro) SI budget = 0: tauxDepassement = "N/A" SINON: depassement = total - budget SI depassement < 0: depassement = 0 tauxDepassement = depassement / budget // ratio (ex. 0,15) // 4) Construire le résumé (sortie stable même si liste vide) resume = { "budget": budget, "total": total, "reste": reste, "repartition": repartition, "alertes": alertes, "tauxDepassement": tauxDepassement, "nbErreurs": taille(erreurs), "erreurs": erreurs } RETOURNER resumeChecklist de vérification
- Préconditions : budget convertible en centimes et ≥ 0 ; listeDepenses peut être vide ; chaque dépense peut être incomplète.
- Invariants : total ≥ 0 ; chaque valeur de repartition ≥ 0 ; total = somme des montants valides ; aucune division par zéro.
- Sorties attendues : résumé toujours présent ; alertes cohérentes (dépassement si total > budget) ; nbErreurs correspond aux dépenses ignorées ; catégories manquantes regroupées sous « Autre ».
Corrections typiques (et comment les éviter)
- Arrondis incohérents : additionner des décimaux puis arrondir à la fin peut créer des écarts ; solution : centimes entiers.
- Division par zéro : calculer un pourcentage avec budget=0 ; solution : branche dédiée “N/A”.
- Catégories vides : clé vide dans le dictionnaire ; solution : normalisation vers “Autre”.
- Montants négatifs acceptés : solution : validation stricte (ou règle explicite si remboursements autorisés, mais alors les tests doivent couvrir ce cas).
- Duplication de validation : parse du montant répété ; solution : fonction
parseCentimes.
Exercice 30 — Planifier une tournée de courses simplifiée
Énoncé
À partir d’une liste d’items (nom, rayon), produire une tournée : regrouper les items par rayon, éviter les doublons, et calculer le nombre total d’articles à prendre. Les entrées peuvent contenir des doublons, des noms avec espaces, des rayons manquants, ou une liste vide.
Décisions de robustesse
- Normalisation des noms : trim + mise en forme cohérente (ex. minuscules) pour détecter les doublons.
- Rayon manquant : ranger dans « Divers ».
- Item invalide (nom vide) : ignorer et signaler.
- Définition de doublon : même nom normalisé, quel que soit le rayon fourni (ou bien doublon par couple (nom, rayon) ; choisir une règle et la tester). Ici : doublon par nom normalisé, et on conserve le premier rayon valide rencontré.
Scénarios de test
| Type | Entrées | Attendu |
|---|---|---|
| Cas normal | [ ("Lait","Frais"), ("Pâtes","Épicerie"), ("Pommes","Fruits") ] | Groupes 3 rayons ; totalArticles=3 ; aucun doublon |
| Cas limite | Liste vide | Tournée vide ; totalArticles=0 ; pas d’erreur |
| Cas limite | [ (" lait ","Frais"), ("LAIT","Frais") ] | Un seul “lait” après dédoublonnage ; totalArticles=1 |
| Cas invalide | [ (" ","Frais"), (null,"Épicerie") ] | Items ignorés ; totalArticles=0 ; erreurs=2 |
| Cas limite | [ ("Savon", ""), ("Dentifrice", null) ] | Rayon “Divers” ; totalArticles=2 ; groupe Divers contient 2 items |
| Cas limite | [ ("Chips","Épicerie"), ("Chips","Snacks") ] | Un seul “chips” ; rayon conservé = “Épicerie” (premier valide) ; totalArticles=1 |
Pseudo-code final commenté
// Tournée de courses simplifiée (robuste) // Entrées : listeItems (chaque item : nomTexte, rayonTexte) // Sorties : plan (groupes par rayon, totalArticles, erreurs) FONCTION normaliserNom(nomTexte): SI nomTexte est manquant: RETOURNER (ECHEC, "nom manquant") n = nomTexte trim SI n est vide: RETOURNER (ECHEC, "nom vide") n = convertir en minuscules(n) RETOURNER (OK, n) FONCTION normaliserRayon(rayonTexte): SI rayonTexte est manquant: RETOURNER "Divers" r = rayonTexte trim SI r est vide: RETOURNER "Divers" RETOURNER r FONCTION planifierTournee(listeItems): erreurs = [] // Pour éviter doublons : ensemble des noms normalisés déjà vus vus = ensemble vide // Groupes : dictionnaire rayon -> liste de noms (format d'affichage) groupes = dictionnaire vide totalArticles = 0 POUR chaque item I dans listeItems: (statutN, nomNorm) = normaliserNom(I.nomTexte) SI statutN = ECHEC: ajouter "Item ignoré: " + message à erreurs CONTINUER SI nomNorm est dans vus: // doublon : on ignore (règle choisie) CONTINUER ajouter nomNorm à vus rayon = normaliserRayon(I.rayonTexte) SI rayon n'existe pas dans groupes: groupes[rayon] = liste vide // On peut stocker une version "jolie" du nom ; ici on garde nomNorm pour simplicité ajouter nomNorm à groupes[rayon] totalArticles = totalArticles + 1 // Sortie stable même si vide plan = { "groupes": groupes, "totalArticles": totalArticles, "nbErreurs": taille(erreurs), "erreurs": erreurs } RETOURNER planChecklist de vérification
- Préconditions : listeItems peut être vide ; chaque item peut avoir nom/rayon manquants.
- Invariants : totalArticles = nombre de noms uniques valides ; aucun nom vide dans les groupes ; chaque item apparaît dans un seul rayon ; groupes peut rester vide sans provoquer d’erreur.
- Sorties attendues : regroupement correct par rayon ; “Divers” utilisé si rayon absent ; doublons supprimés selon la règle ; nbErreurs correspond aux items invalides ignorés.
Corrections typiques (et comment les éviter)
- Doublons non détectés à cause des espaces/majuscules : solution : normaliser le nom avant comparaison.
- Rayon vide crée une clé vide : solution : normaliser vers “Divers”.
- Comptage incorrect : incrémenter total avant le dédoublonnage ; solution : incrémenter uniquement après validation + ajout à l’ensemble
vus. - Accès à des champs manquants : solution : fonctions de normalisation qui gèrent null/absent.
- Règle de doublon ambiguë : solution : écrire la règle en une phrase et la couvrir par un test (ex. “Chips” dans deux rayons).