PROFDINFO.COM

Votre enseignant d'informatique en ligne

La liste générique

Voici une solution possible pour le laboratoire de la liste générique. L'histoire de sa création est amusante: lorsque j'ai créé l'énoncé du laboratoire 1 je l'ai envoyé à quelques amis à qui ça pourrait rappeler de bons souvenirs (tout bon programmeur a des bons souvenirs de listes doublement chaînées et d'arbres binaires...).

L'un d'entre eux, Gabor Ketseti, m'a surpris en m'expédiant quatre jours plus tard sa solution au laboratoire 1, comme s'il était un élève de ma classe! Son commentaire: "Je ne déteste pas faire des projets de style académique de temps à autres. J’ai fait ce code afin que tu puisses éventuellement le montrer à tes étudiants pour leur donner une idée de comment un professionnel approcherait le problème (si ça te chantes bien sur!)".

Parlez-moi d'un bon ami, qui non seulement s'amuse à faire mes travaux pour se détendre mais qui a en plus à coeur le succès de mon cours!

J'étais bien content mais le problème était que Gabor m'avait devancé et il avait tout fait ça en programmation générique, que je ne devais pas couvrir avant quelques semaines encore. J'ai donc mis sa solution de côté en attendant le bon moment pour la sortir... et ce bon moment est maintenant!

Qu'avons-nous à en apprendre?

La première leçon à tirer de l'enseignement du grand maître Ketseti (programmeur émérite depuis plus de 10 ans et qui travaille en ce moment chez A2M) est que la généricité, c'est cool. On peut supposer qu'il a peut-être opté pour une solution générique pour impressionner mes étudiants mais, même si ce n'est peut-être pas faux, il est bien évident que si on a à créer une collection de quelque chose, mieux vaut en faire une collection générique pour augmenter sa réutilisabilité.

Les explications concernant la façon de faire une collection générique et de l'utiliser, incluant la création d'itérateurs et de délégués ont été largement couvertes en classe, en plus d'être vues ici et ici. Nous ne nous y attarderons donc pas maintenant.

Il y a toutefois plusieurs notions connexes intéressantes dans le code de Gabor...

Les commentaires de documentation XML

Il est possible de placer dans notre code des balises pour la documentation XML. Ces balises ont deux usages courants: créer un fichier de documentation XML automatique, et fournir des informations à IntelliSense pendant que l'on code.

Un commentaire de documentation XML commence par ///. Il peut contenir n'importe quelles balises XML valides, mais il en existe plusieurs qui sont couramment utilisées de façon standard. Celles-ci sont même utilisées par IntelliSense et vérifiées par le compilateur, qui donnera des avertissements si on ne les utilise pas correctement.

Par exemple:

/// <summary>
/// Recherche un noeud correspondant aux critères donnés en commençant par le début de la liste
/// </summary>
/// <param name="trouve">Critères de recherche</param>
/// <returns>Le noeud correspondant aux critères ou null si aucun noeud ne correspond aux critères</returns>
public Noeud Trouver(prédicat trouve)
{
   // ...
}

La balise <summary> devra contenir une description de la méthode ou propriété expliquant clairement à quoi elle sert.

Les balises <param> donneront des informations sur les paramètres (le nom du paramètre sera placé dans l'attribut name de la balise et sa description après la balise. On ne mentionnera normalement pas le type du paramètre. Si on a plusieurs paramètres, on créera une balise <param> pour chacun d'eux.

La balise <returns> indique ce que retourne la méthode (on l'omet si c'est void).

D'autres balises utiles: <exception>, <typeparam> (idéal pour nos T) et <example>.

Pour créer le fichier de documentation XML, il suffit d'aller modifier les propriétés de notre projet, dans la section "Générer", cocher la case "Fichier de documentation XML" et lui fournir un chemin. Le fichier sera recréé à chaque génération.

La méthode Debug.Assert

L'objet Debug se trouve dans l'espace de nom System.Diagnostics. Sa méthode Assert est statique et nous permet de nous assurer qu'une condition est respectée à un certain point du programme. Si ce n'est pas le cas, le programme s'arrêtera, nous affichera un message d'erreur (que l'on peut personnaliser), le contenu de la pile d'exécution puis nous permettra de poursuivre l'exécution (Ignorer), de passer en mode débogage (Recommencer) ou de tout arrêter (Abandonner).

Erreur d'assertion

Assert accepte deux paramètres: une condition à vérifier et un message d'erreur (facultatif) à afficher si la condition est fausse.

On l'utilise seulement pendant le codage et les tests afin de nous assurer que tout se passe normalement - cette fonction ne doit surtout pas être utilisée pour valider un choix de l'usager! En effet, lorsque l'on compilera le code en mode Release, les appels à Assert ne seront même pas compilés (exactement comme si on les effaçait avant de compiler).

Par exemple, ici:

public void Insérer(T valeur, PositionRelative positionRelative)
{
   Noeud nouveau = new Noeud(valeur);
   
   // si la liste est vide
   if (_fin == null)
   {
      Debug.Assert(_début == null); // le début devrait également null 
      
      _fin = _début = nouveau;
      return;
   }
 	  
   // ...
}

On sait que lorsque le pointeur de fin de liste est null, c'est que la liste est vide. Si la liste est vide, le pointeur de début de liste doit nécessairement être null lui aussi. Il est inutile de vérifier cette condition avec un if qui écrirait un message d'erreur lorsque ce n'est pas le cas - en effet, une fois nos tests terminés on fera confiance à notre code et on saura d'avance que cette situation ne peut pas se produire.

Toutefois pendant le codage ou les tests, on n'est certain de rien. Un simple appel à Debut.Assert nous permet de vérifier si la condition "_début == null" est vraie comme elle devrait l'être et de nous avertir sinon (remarquez que Gabor n'a pas donné de message d'erreur personnalisé, mais il aurait pu). Lorsque l'on compilera tout ça en mode Release, cette ligne disparaîtra d'elle-même.

Les enums

J'avais mentionné la possibilité d'utiliser un enum pour définir les positions "avant" et "après" lors de l'insertion et j'ai découvert que plusieurs d'entre vous n'étiez pas familiers avec le concept.

Notre méthode d'insertion générique insère soit avant ou après une liste ou un noeud. Comment lui passer cette valeur "avant" ou "après"?

On peut bien entendu utiliser un string qui contient le mot voulu, mais à ce moment il faut valider que le string contient bien un des mots permis et penser à notre réaction si ce n'est pas le cas.

On pourrait utiliser un nombre entier, mais là encore on pourrait recevoir des valeurs ne correspondant à rien et en plus utiliser "1" et "2" est beaucoup moins parlant que "avant" et "après".

On peut aussi utiliser un booléen, qui n'a que deux valeurs possibles et ne demande donc aucune validation, mais on garde alors le désavantage d'avoir des valeurs qui ne nous disent rien à prime abord... (une position "fausse" équivaut à avant ou à après?)

La bonne solution à ça est de créer un enum. Un enum représente une énumération de valeurs possibles. Cette énumération devient un genre de type de données, ce qui empêche un utilisateur de notre méthode de donner des valeurs différentes de celles permises, exactement comme il est impossible de passer 32 lorsque notre méthode attend un booléen.

Un enum est très facile à déclarer:

public enum PositionRelative
{
   avant,
   après
}

On lui donne un nom et on énumère des valeurs d'un même type, en quantité désirée. Après coup, notre enum peut être traité comme un type de données:

public void Insérer(T valeur, PositionRelative positionRelative)
{
   //...
}

Le paramètre positionRelative est de type PositionRelative (notez la majuscule initiale!), c'est à dire qu'il pourra contenir les valeurs PositionRelative.avant ou PositionRelative.après, mais rien d'autre - aucune validation nécessaire.

En réalité, chaque valeur dans un enum correspond à un nombre entier. La première valeur reçoit 0, les suivantes un de plus que la précédente. Il est possible de caster une valeur d'enum en int, ou de forcer certaines valeurs à correspondre à certains nombres, mais de façon générale on n'a aucun besoin de faire ça. L'idée d'utiliser un enum est justement de se simplifier la vie.

Les tests

Remarquez le programme de test créé par Gabor. Lorsque l'on teste un programme, il vaut la peine d'être méticuleux et de procéder de façon ordonnée afin de ne pas laisser passer d'erreurs.

On a quatre cas d'utilisation précis:

De plus, on a trois opérations à tester, chacune en deux variantes:

Sachant cela, on peut aisément bâtir une batterie de tests complète et efficace qui nous assurera une note parfaite!