par , 27/10/2014 à 20h41 (2364 Affichages)
Je cherchais une idée d'article pour tester cette nouvelle fonctionnalité de Developpez.com (au passage, saluons Anomaly et les personnes l'ayant aidé !). J'ai répondu à cette discussion sur les forums aujourd'hui et cela m'a donné une idée : comment gérer de manière élégante des tests d'appartenances à des plages de valeurs, en C ?
Le problème exposé par le PO (posteur initial ^^) est le suivant :
Ecrire un programme qui demande l'âge de l'utilisateur et affiche sa catégorie :
- Poussin : 6-7 ans
- Pupille : 8-9 ans
- Minime : 10-11 ans
- Cadet : 12+
- Pas de catégorie : 5 ans et -
De manière plus large, on raisonnera sur l'appartenance d'une valeur donnée à des plages possibles. Le titre parle de C mais les principes sont génériques et applicables à d'autres langages.
La technique qui ressort de cette discussion est de passer par de longues listes de cases à l'intérieur d'un switch sur l'âge. Comme je le dis dans mon message en #18, je pense que cette technique est mauvaise. La maintenance est quasiment impossible quand on rajoute des plages et qu'on souhaite changer les bornes, il est beaucoup trop facile d'avoir des cas en double, la gestion de plages non continues est très compliquée.
GCC propose une extension intéressante, comme montrée au message #12 : les Case Ranges. Elle peut rendre élégante et compacte la solution switch / case et donc acceptable. Elle a l'inconvénient de perdre la portabilité...
La meilleure solution est sans doute de passer par une structure if / else if / else et de multiplier les else if au fur et à mesure que des plages se rajoutent. A l'indentation près, on obtient quelque chose de semblable avec des if / else imbriqués mais la différence est que le code devient illisible et donc dur à maintenir si des plages s'ajoutent ; il faut donc bien utiliser des else if.
Pour être encore plus élégant, on peut déléguer les tests à des fonctions. La structure devient alors très claire à lire, proche d'un énoncé humain : si c'est un poussin, si c'est un minime, etc. Si un jour les tests deviennent un peu plus complexes que deux inégalités (par exemple, si une catégorie est est l'union de deux plages non continues ou si les catégories diffèrent selon si on est une fille ou un garçon), il est aisé de changer les fonctions sans toucher à la structure conditionnelle. De plus, ces fonctions resserviront sans doute ailleurs dans le code. On retrouve deux principes récurrents de la programmation : encapusuler les détails pouvant changer dans le futur et factoriser ce qui est redondant.
Voici ce que ça peut donner en C :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| #include <stdbool.h>
#include <stdio.h>
#define ENTREE_POUSSIN 6
#define ENTREE_PUPILLE 8
#define ENTREE_MINIME 10
#define ENTREE_CADET 12
typedef unsigned int age_t;
bool isPoussin(age_t age)
{
return age >= ENTREE_POUSSIN && age < ENTREE_PUPILLE;
}
bool isPupille(age_t age)
{
return age >= ENTREE_PUPILLE && age < ENTREE_MINIME;
}
bool isMinime(age_t age)
{
return age >= ENTREE_MINIME && age < ENTREE_CADET;
}
bool isCadet(age_t age)
{
return age >= ENTREE_CADET;
}
int main(void)
{
age_t age = 0;
puts("Age ?");
scanf("%u", &age); // pas terrible mais simple pour cet exemple
printf("Il a %u ans ?\n", age);
if(isPoussin(age))
{
puts("C'est un poussin !");
}
else if(isPupille(age))
{
puts("C'est un pupille !");
}
else if(isMinime(age))
{
puts("C'est un minime !");
}
else if(isCadet(age))
{
puts("C'est un cadet !");
}
else
{
puts("Too young to die, to old to rock n' roll...");
// https://www.youtube.com/watch?v=Rwn0R1PFUwU
}
} |
$ c99 conditional.c
$ ./a.out
Age ?
10
Il a 10 ?
C'est un minime !
Le mérite d'une telle solution ne m'est pas totalement imputable : je reprend les principes énoncés par Steve McConnel au chapitre 15 ("Using Conditionals") de son livre Code Complete (livre dont je conseille la lecture à tout personne trempant dans l'informatique au sens large).