cursus.

Cours 5 · Mise en pratiqueLeçon 2 sur 2

Projet

3 h de lecture10 sections Version PDF

À la fin de cette leçon, vous saurez

Analyser un énoncé, en tirer un diagramme de classes, implémenter par incréments, tester, et restituer.

L'énoncé tient en trois lignes :

Réaliser une application de gestion de bibliothèque. Les adhérents empruntent des ouvrages pour une durée limitée. Un ouvrage peut exister en plusieurs exemplaires. Les retards donnent lieu à une pénalité.

Le réflexe est d'ouvrir l'éditeur et d'écrire public class Main. C'est la façon la plus sûre de produire, trois semaines plus tard, une classe de huit cents lignes que personne ne peut modifier.

Ce dernier chapitre n'introduit aucune notion. Il dit ce qu'on fait avant d'écrire la première ligne, et dans quel ordre — c'est le seul moment du semestre où l'on conçoit au lieu d'appliquer.

Analyser l'énoncé

La méthode est vieille, un peu naïve, et elle fonctionne : souligner les noms, entourer les verbes.

Les noms communs sont des candidats-classes : adhérent, ouvrage, exemplaire, emprunt, pénalité, bibliothèque. Les verbes sont des candidats-méthodes : emprunter, rendre, calculer une pénalité, rechercher.

Vient ensuite le filtrage, et c'est là que le travail commence — un nom n'est pas toujours une classe.

« Durée » n'est pas une classe : c'est un attribut d'Emprunt, ou une constante de la bibliothèque. Un nom qui n'a ni comportement ni identité propre est une donnée.

« Pénalité » est douteux. Si c'est un simple montant, c'est une valeur calculée. Si elle a une date, un statut payé ou non, un historique — alors elle a une identité, et c'est une classe. L'énoncé ne tranche pas : c'est une question à poser, et savoir qu'il faut la poser vaut mieux que deviner.

« Ouvrage » et « exemplaire » sont deux classes distinctes, et c'est la vraie difficulté de cet énoncé. « Un ouvrage peut exister en plusieurs exemplaires » dit exactement cela : Dune est un ouvrage — un titre, un auteur, un ISBN — dont la bibliothèque possède trois exemplaires physiques, chacun avec sa cote et son état. On n'emprunte pas un ouvrage, on emprunte un exemplaire.

Les fusionner est l'erreur numéro un sur cet énoncé, et elle se paie immédiatement : on ne sait plus dire combien d'exemplaires sont disponibles.

Le filtre final est celui du chapitre 1 : chaque classe retenue doit se décrire en une phrase sans « et ». Si l'on n'y arrive pas, il y en a deux.

Concevoir le diagramme

On dessine ensuite, avec la notation du chapitre 8 — et l'on vise cinq à huit classes. Au-delà sur un projet de L2, c'est généralement qu'on a modélisé des détails.

Adherent  1 ───── 0..*  Emprunt  0..*  ─────  1  Exemplaire  *  ─────  1  Ouvrage

Trois décisions se lisent dans cette seule ligne, et chacune était une question ouverte.

Emprunt est devenu une classe à part entière, et non une simple association. La raison est qu'il porte des données propres — date de début, date de retour prévue, date de retour effective — et un comportement : calculer son retard. Une association qui porte des attributs est une classe.

La multiplicité 0..* du côté Emprunt dit qu'un adhérent peut n'avoir aucun emprunt en cours, et plusieurs à la fois. Un 0..3 dirait « trois au maximum », ce qui est une règle métier qu'il faut alors faire respecter quelque part.

Et Exemplaire → Ouvrage en * ── 1 traduit exactement la phrase de l'énoncé.

Une fois le diagramme posé, on le vérifie en y faisant marcher un scénario : « Ana emprunte Dune, le rend avec cinq jours de retard, paie sa pénalité ». On suit le chemin sur le dessin. Si une information manque — comment retrouver un exemplaire libre de cet ouvrage ? — le diagramme est incomplet, et il vaut mieux s'en apercevoir maintenant.

Implémenter par incréments

C'est ici que la plupart des projets de L2 se perdent, et l'erreur est toujours la même : écrire toutes les classes, puis tous les attributs, puis toutes les méthodes, et essayer de faire tourner l'ensemble la veille du rendu. Rien ne compile jamais avant la fin, et le premier test a lieu quand il est trop tard.

La méthode qui marche est la tranche verticale : choisir un scénario complet, le faire fonctionner de bout en bout, puis passer au suivant.

Incrément 1   créer un ouvrage, un exemplaire, les afficherIncrément 2   un adhérent emprunte un exemplaire disponibleIncrément 3   le refus si l'exemplaire est déjà emprunté   (exceptions)Incrément 4   le retour, et le calcul du retardIncrément 5   la pénalité, et l'historiqueIncrément 6   la recherche par titre et par auteur          (collections)

Trois bénéfices, et le troisième est le plus important.

Le programme compile et s'exécute en permanence. Une régression se détecte le jour même, pas trois semaines plus tard.

On a toujours quelque chose à montrer. Un projet à moitié fait mais qui tourne vaut mieux qu'un projet complet qui ne compile pas — c'est vrai pour la note comme pour la suite.

La conception se corrige en marchant. L'incrément 3 révélera peut-être qu'il faut une classe Reservation à laquelle on n'avait pas pensé. La découvrir en écrivant coûte une heure ; la découvrir à la fin coûte une refonte.

Tester

Un jeu de tests, même minimal, n'est pas un supplément : c'est ce qui permet de modifier sans casser.

Quatre choses méritent d'être testées dans un programme objet, et elles ne sont pas les mêmes qu'en programmation impérative.

Les invariants du chapitre 3 : un constructeur refuse-t-il bien un ISBN vide, un solde négatif, une date de retour antérieure à la date d'emprunt ?

Les cas limites, comme au chapitre 10 du cours de programmation : emprunter le dernier exemplaire disponible, rendre un jour pile après l'échéance, une liste vide.

Le comportement polymorphe : une méthode redéfinie fait-elle bien ce qu'elle doit dans chaque sous-classe ? C'est le seul endroit où le chapitre 6 peut se vérifier mécaniquement.

Les exceptions : la bonne exception est-elle levée dans le bon cas ? Un test qui vérifie qu'un emprunt impossible échoue vaut autant qu'un test de succès.

Un dernier bénéfice, moins évident : écrire un test, c'est être le premier client de sa propre API. Si le test est pénible à écrire — s'il faut créer sept objets pour en tester un — la conception est trop couplée. Le test révèle le défaut avant l'utilisateur.

Restituer

Le rendu compte, et il tient en peu de chose.

Un fichier de description — ce que fait le programme, comment le compiler et le lancer, ce qui est fait et ce qui ne l'est pas. La dernière partie est celle qu'on omet, et c'est la plus appréciée : un projet honnête sur ses limites est jugé plus favorablement qu'un projet qui les cache.

Le diagramme de classes, tel qu'il est à la fin — pas celui du début. S'ils diffèrent, c'est normal, et l'écart vaut d'être expliqué en deux phrases : c'est la preuve qu'on a conçu en marchant.

Un jeu de données de démonstration, pour que le correcteur n'ait pas à en inventer.

Les cinq pièges classiques

Ils reviennent chaque année, et chacun contredit un chapitre précis.

La classe divine : un Main de six cents lignes qui fait tout, entouré de classes anémiques réduites à des getters. C'est le chapitre 1 et le chapitre 3 ignorés — et la marque distinctive est que les autres classes n'ont aucune méthode intéressante.

L'encapsulation absente : tout en public, y compris les attributs, parce que « c'est plus simple pour y accéder depuis le Main ». Cela signale généralement le piège précédent.

L'héritage partout : une hiérarchie de six niveaux, parce qu'on a trouvé des attributs communs. Le test « est-un » du chapitre 5 en élimine la moitié, et la composition règle le reste.

Le grand assemblage final : voir la section précédente.

L'oubli du diagramme : le code est écrit d'abord, le diagramme dessiné la veille pour satisfaire à la consigne. Il ne sert alors à rien — et cela se voit.

Quiz · vérifiez votre compréhension Sans réponse

Sur l'énoncé « un ouvrage peut exister en plusieurs exemplaires », faut-il une ou deux classes ?

Quiz · vérifiez votre compréhension Sans réponse

Pourquoi implémenter par tranches verticales plutôt que classe par classe ?

À vous

L'exercice conduit l'analyse de l'énoncé d'ouverture, du texte au modèle.

D'abord l'extraction : le programme repère les noms et les verbes, et vous décidez, pour chacun, s'il devient une classe, un attribut, une méthode, ou rien. Le corrigé justifie chaque verdict — y compris les deux cas douteux, « pénalité » et « durée ».

Ensuite la vérification du modèle par un scénario : « Ana emprunte Dune, le rend avec cinq jours de retard ». Vous faites marcher le scénario sur le diagramme décrit en données, et le programme signale ce qui manque pour le mener à bout.

Enfin un contrôleur de conception, appliqué à deux versions du même projet : il compte les méthodes par classe, repère la classe divine, les attributs publics et les hiérarchies trop profondes. Vous verrez qu'un modèle correct et un modèle raté se distinguent à des chiffres simples.

Exercice · JavaScript · à vous de jouer

Tirez un modèle d'un énoncé, éprouvez-le par un scénario, puis auditez deux conceptions.

En attente
const ENONCE = [
  "Réaliser une application de gestion de bibliothèque.",
  "Les adhérents empruntent des ouvrages pour une durée limitée.",
  "Un ouvrage peut exister en plusieurs exemplaires.",
  "Les retards donnent lieu à une pénalité.",
].join(" ");

// ── 1. Du texte aux candidats ─────────────────────────────────────────────
const NOMS = ["bibliothèque", "adhérent", "ouvrage", "durée", "exemplaire", "retard", "pénalité"];
const VERBES = ["emprunter", "rendre", "calculer", "rechercher"];

// Pour chaque nom : "classe", "attribut", ou "à decider" (question à poser).
const VERDICTS = {
  // ← à écrire, avec une justification en une ligne
};

// ── 2. Le modèle, décrit en données ───────────────────────────────────────
const MODELE = {
  classes: {
    Adherent:   { attributs: ["nom"], methodes: ["emprunter", "rendre"] },
    Ouvrage:    { attributs: ["titre", "auteur", "isbn"], methodes: [] },
    Exemplaire: { attributs: ["cote", "etat", "disponible"], methodes: ["estDisponible"] },
    Emprunt:    { attributs: ["debut", "prevu", "effectif"], methodes: ["retardEnJours", "penalite"] },
  },
  relations: [
    { de: "Adherent", vers: "Emprunt", mult: "0..*" },
    { de: "Emprunt", vers: "Exemplaire", mult: "1" },
    { de: "Exemplaire", vers: "Ouvrage", mult: "1" },
  ],
};

// Fait marcher un scénario sur le modèle et signale ce qui manque.
function verifierScenario(modele, etapes) {
  const manques = [];
  for (const e of etapes) {
    const c = modele.classes[e.classe];
    if (!c) { manques.push("classe absente : " + e.classe); continue; }
    // ← à écrire : vérifier que la méthode et les attributs requis existent
  }
  return manques;
}

const SCENARIO = [
  { classe: "Adherent",   methode: "emprunter",     attributs: [] },
  { classe: "Exemplaire", methode: "estDisponible", attributs: ["disponible"] },
  { classe: "Emprunt",    methode: "retardEnJours", attributs: ["prevu", "effectif"] },
  { classe: "Emprunt",    methode: "penalite",      attributs: [] },
  { classe: "Ouvrage",    methode: "exemplairesLibres", attributs: [] },   // manque ?
];

// ── 3. Mesurer une conception ─────────────────────────────────────────────
const PROJET_RATE = {
  Main:       { methodes: 34, attributsPublics: 12, profondeur: 0 },
  Livre:      { methodes: 0,  attributsPublics: 5,  profondeur: 0 },
  Adherent:   { methodes: 0,  attributsPublics: 4,  profondeur: 0 },
  LivrePoche: { methodes: 1,  attributsPublics: 0,  profondeur: 4 },
};
const PROJET_CORRECT = {
  Bibliotheque: { methodes: 6, attributsPublics: 0, profondeur: 0 },
  Ouvrage:      { methodes: 4, attributsPublics: 0, profondeur: 0 },
  Exemplaire:   { methodes: 5, attributsPublics: 0, profondeur: 0 },
  Emprunt:      { methodes: 5, attributsPublics: 0, profondeur: 0 },
  Adherent:     { methodes: 5, attributsPublics: 0, profondeur: 0 },
};

function auditer(nom, projet) {
  // ← à écrire : signaler la classe divine, les classes anémiques, les
  //   attributs publics et les hiérarchies de plus de trois niveaux.
  console.log("   " + nom + " : à auditer");
}

// ── À VOUS ────────────────────────────────────────────────────────────────
// 1. Remplissez VERDICTS pour les sept noms.
// 2. Complétez verifierScenario : que manque-t-il pour mener le scénario ?
// 3. Écrivez auditer() et comparez les deux projets.

console.log(ENONCE);

Console de sortie
Le résultat s'affiche dans la console

En travaux pratiques

Travaux pratiques 10 · sur machine

Rendre le projet, et savoir le défendre

Assembler la médiathèque des neuf séances en un projet livrable, testé, et dont chaque décision de conception peut être justifiée à l'oral.

3 h
Avant de commencer
  • Les TP 1 à 9
  • Un dépôt Git, et JUnit
  1. 1. L'inventaire

    Listez ce que votre médiathèque fait aujourd'hui, et ce que l'énoncé demandait. Signalez explicitement les manques plutôt que de les laisser découvrir.

  2. 2. Séparer les couches

    Réorganisez en trois paquetages : domaine, persistance, interface. Vérifiez qu'aucune classe du domaine n'importe rien de l'interface.

  3. 3. Tester le domaine

    Écrivez des tests sur les règles métier : emprunt d'un document indisponible, quota d'adhérent, calcul de pénalité, retour. Un test par règle.

  4. 4. Tester les cas limites

    Ajoutez les cas limites : catalogue vide, adhérent sans emprunt, retard de zéro jour, emprunt le jour même du retour, document rendu deux fois.

  5. 5. Persister

    Sauvegardez et rechargez le catalogue dans un fichier. Vérifiez qu'un aller-retour complet redonne exactement l'état initial.

  6. 6. Le remplacement

    Écrivez une seconde implémentation de la persistance, dans un autre format. Branchez-la SANS modifier une seule ligne du domaine.

  7. 7. Relire son propre code

    Cherchez dans votre projet : un accesseur inutile, une cascade de tests de type, une classe qui fait deux choses, un catch vide. Corrigez ce que vous trouvez.

  8. 8. Préparer la soutenance

    Pour trois décisions de conception, préparez la justification et l'alternative que vous avez écartée. C'est ce qui distingue un projet compris d'un projet recopié.

C'est réussi quand
  • Aucune classe du domaine n'importe quoi que ce soit de l'affichage ou du stockage
  • Vos tests échouent si vous cassez volontairement une règle métier
  • La seconde persistance se branche sans toucher au domaine
  • Vous savez défendre trois choix et nommer ce que vous avez écarté

Ce que ce semestre laisse

Dix chapitres plus tôt, la question était de passer du programme qui calcule au programme qui modélise.

Le bloc I a montré ce qui casse sans objet, et donné la classe. Le bloc II l'a rendue responsable de sa propre cohérence. Le bloc III — le cœur — a permis de factoriser sans abuser du « est-un », puis d'écrire du code qui traite uniformément des objets de types différents. Le bloc IV a rendu tout cela robuste et dessinable, et le bloc V l'a fait vivre.

Reste une idée qui traverse les cinq blocs, et c'est peut-être ce qu'il faut en garder : la programmation objet consiste à décider où mettre chaque chose. Quelle classe porte quelle donnée, quelle classe porte quelle règle, ce qui est visible et ce qui ne l'est pas, ce qui est commun et ce qui varie. Le langage ne prend aucune de ces décisions à votre place — il se contente de rendre les bonnes faciles à écrire et les mauvaises faciles à regretter.

Et le critère de réussite est resté le même depuis le chapitre 1 : quand une exigence change, combien de fichiers faut-il ouvrir ? Un seul, si la chose était au bon endroit.

À retenir

Flashcards · 1 / 6Toucher pour retourner
Fin de la leçon

Vous avez parcouru les 10 sections.

Marquez-la terminée pour faire avancer votre parcours, ou revenez sur un point avant de passer à la suite.