Files
2026_TD2_DEV51_Qualite_Algo/COMPTE_RENDU.md
T

212 lines
7.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Compte rendu – TD Complexité cyclomatique (R5.A.04)
## Partie 1 – Graphes de flot de contrôle
Pour chaque fonction demandée dans l'énnoncé, on construit le graphe de flot de contrôle, puis on applique :
**M = E − N + 2P** (E = arêtes, N = nœuds, P = 1 pour une fonction)
On vérifie ensuite le résultat avec la règle pratique **M = décisions + 1**.
### C2 – `moyenne` (C)
![Graphe C2](Graphe/C2.jpg)
| Nœud | Contenu |
|---|---|
| 1 | `somme = 0 ; i = 0` |
| 2 | décision `i < n` |
| 3 | `somme += t[i]` |
| 4 | `i++` (arête de retour vers 2) |
| 5 | décision `n > 0` (ternaire) |
| 6 | `somme / n` |
| 7 | `0` |
| 8 | sortie (`return`) |
- N = 8, E = 9, P = 1
- **M = 9 − 8 + 2 × 1 = 3**
- Vérification : 1 `for` + 1 ternaire `?:` + 1 = 3
### C4 – `nom_jour` (C)
![Graphe C4](Graphe/C4.jpg)
| Nœud | Contenu |
|---|---|
| 1 | `switch (jour)` |
| 2 à 8 | `case 1` à `case 7` : `return "lundi"` … `return "dimanche"` |
| 9 | `default` : `return "inconnu"` |
| 10 | sortie |
- N = 10, E = 16 (8 arêtes du `switch` vers chaque branche + 8 arêtes des branches vers la sortie), P = 1
- **M = 16 − 10 + 2 × 1 = 8**
- Vérification : 7 `case` + 1 = 8 (le `default` ne compte pas : c'est le « sinon » du `switch`)
### J2 – `estDisponible` (Java)
![Graphe J2](Graphe/J2.png)
La méthode ne contient aucun `if`, mais les `&&` sont évalués paresseusement : chaque opérande est une décision (si `livre.emprunte` est vrai, on sort directement sans évaluer la suite).
| Nœud | Contenu |
|---|---|
| 1 | décision `!livre.emprunte` |
| 2 | décision `!livre.reserve` |
| 3 | décision `!livre.abime` |
| 4 | sortie (`return`) |
- N = 4, E = 5, P = 1
- **M = 5 − 4 + 2 × 1 = 3**
- Vérification : 2 opérateurs `&&` + 1 = 3
### J6 – `importer` (Java)
![Graphe J6](Graphe/J6.png)
Les nœuds de décision sont en jaune, et la condition de la boucle `for` est en bleu.
| Nœud | Contenu |
|---|---|
| 1 | `importes = 0` |
| 2 | décision `for` : reste-t-il une ligne ? |
| 3 | décision `ligne.isBlank()` |
| 4 | décision `ligne.startsWith("#")` (le `\|\|`) |
| 5 | `continue` |
| 6 | `champs = ligne.split(";")` |
| 7 | décision `champs.length != 4` |
| 8 | `System.err.println(...)` + `continue` |
| 9 | `try` : `annee = Integer.parseInt(...)` |
| 10 | décision `annee > 0` |
| 11 | décision `annee <= 2026` (le `&&`) |
| 12 | `livres.add(...)` + `importes++` |
| 13 | décision `catch` : `NumberFormatException` levée ? |
| 14 | `System.err.println("Année invalide ...")` |
| 15 | sortie (`return importes`) |
Arêtes : 1→2, 2→3, 2→15, 3→4, 3→5, 4→5, 4→6, 5→2, 6→7, 7→8, 7→9, 8→2, 9→10, 10→11, 10→13, 11→12, 11→13, 12→13, 13→2, 13→14, 14→2.
- N = 15, E = 21, P = 1
- **M = 21 − 15 + 2 × 1 = 8**
- Vérification : 1 `for` + 1 `if` + 1 `||` + 1 `if` + 1 `if` + 1 `&&` + 1 `catch` = 7 décisions, + 1 = 8
> Cas particuliers : le `||` et le `&&` comptent chacun comme une décision (évaluation paresseuse), et le `catch` aussi, puisqu'il crée un second chemin de sortie du `try`. Les `continue` ne comptent pas : ils renvoient simplement vers la condition de la boucle.
### P2 – `signe` (Python)
![Graphe P2](Graphe/P2.jpg)
| Nœud | Contenu |
|---|---|
| 1 | décision `x > 0` |
| 2 | `return 1` |
| 3 | décision `x < 0` (`elif`) |
| 4 | `return -1` |
| 5 | `return 0` |
| 6 | sortie |
- N = 6, E = 7, P = 1
- **M = 7 − 6 + 2 × 1 = 3**
- Vérification : 1 `if` + 1 `elif` + 1 = 3
### P3 – `notes_valides` (Python)
![Graphe P3](Graphe/P3.jpg)
La compréhension de liste cache une boucle et un filtre : elle se lit comme un `for` contenant un `if`.
| Nœud | Contenu |
|---|---|
| 1 | initialisation de la liste résultat |
| 2 | décision : reste-t-il un élément `n` dans `notes` ? |
| 3 | décision `0 <= n <= 20` |
| 4 | ajout de `n` au résultat (retour vers 2) |
| 5 | sortie (`return`) |
- N = 5, E = 6, P = 1
- **M = 6 − 5 + 2 × 1 = 3**
- Vérification : 1 `for` + 1 `if` (dans la compréhension) + 1 = 3
> Remarque : `0 <= n <= 20` est une comparaison chaînée, équivalente à `0 <= n and n <= 20`. Si on compte ce `and` implicite comme une décision, on obtient M = 4. Nous l'avons compté comme une seule condition, comme le font la plupart des outils (aucun opérateur `and` n'apparaît dans le code).
### Récapitulatif
| Fonction | N | E | M | Classe |
|---|---|---|---|---|
| C2 `moyenne` | 8 | 9 | 3 | Simple |
| C4 `nom_jour` | 10 | 16 | 8 | Simple |
| J2 `estDisponible` | 4 | 5 | 3 | Simple |
| J6 `importer` | 15 | 21 | 8 | Simple |
| P2 `signe` | 6 | 7 | 3 | Simple |
| P3 `notes_valides` | 5 | 6 | 3 | Simple |
## Partie 2 – Comptage des décisions (M = décisions + 1)
### C (`exercices.c`)
| Fonction | Décisions | M | Classe |
|---|---|---|---|
| C1 `carre` | aucune | **1** | Simple |
| C2 `moyenne` | 1 `for`, 1 ternaire `?:` | **3** | Simple |
| C3 `est_bissextile` | 1 `&&`, 1 `\|\|` | **3** | Simple |
| C4 `nom_jour` | 7 `case` | **8** | Simple |
| C5 `compter_mots` | 1 `while`, 4 `\|\|`, 1 `if`, 1 `else if` | **8** | Simple |
| C6 `date_valide` | 4 `if`/`else if`, 6 `\|\|` | **11** | Modéré |
Détail de C3 : il n'y a aucun `if`, mais `&&` et `||` sont évalués paresseusement : chacun crée un branchement (si `annee % 4 == 0` est faux, `annee % 100 != 0` n'est pas évalué). 2 décisions + 1 = **3**, ce qui correspond au graphe vu en cours (N = 4, E = 5).
Détail de C4 : le `default` ne compte pas (c'est le « sinon » du `switch`) et les `return` non plus. 7 + 1 = **8**, comme avec le graphe (16 − 10 + 2).
Détail de C5 :
| Ligne | Décision | +1 |
|---|---|---|
| `while (texte[i] != '\0')` | `while` | 1 |
| `c == ' ' \|\| c == '\t' \|\| c == '\n' \|\| c == ',' \|\| c == '.'` | 4 `\|\|` | 5 |
| `if (separateur)` | `if` | 6 |
| `else if (!dans_mot)` | `else if` | 7 |
**M = 7 + 1 = 8.** Ici aussi les `||` comptent sans `if` : ils sont dans une affectation (`int separateur = ...`), mais restent des branchements.
Détail de C6 :
| Ligne | Décision | +1 |
|---|---|---|
| `if (annee < 1 \|\| mois < 1 \|\| mois > 12 \|\| jour < 1)` | `if` + 3 `\|\|` | 4 |
| `if (mois == 2)` | `if` | 5 |
| `if (est_bissextile(annee))` | `if` | 6 |
| `else if (mois == 4 \|\| mois == 6 \|\| mois == 9 \|\| mois == 11)` | `else if` + 3 `\|\|` | 10 |
**M = 10 + 1 = 11**, soit juste au-dessus du seuil de 10 (McCabe, PMD) : c'est la fonction C la plus complexe. L'appel à `est_bissextile` ne compte pas : ses décisions appartiennent à `est_bissextile` (M calculé par fonction).
### Python (`exercices.py`)
| Fonction | Décisions | M | Classe |
|---|---|---|---|
| P1 `celsius_vers_fahrenheit` | aucune | **1** | Simple |
| P2 `signe` | 1 `if`, 1 `elif` | **3** | Simple |
| P3 `notes_valides` | 1 `for`, 1 `if` (dans la compréhension) | **3** | Simple |
| P4 `mention` | 1 `for`, 1 `if` | **3** | Simple |
| P5 `analyser_mot_de_passe` | 2 `for`, 8 `if`, 2 `elif`, 2 `and`, 1 ternaire | **16** | Modéré |
Détail de P5 :
| Ligne | Décision | +1 |
|---|---|---|
| `if len(mdp) < 12` | `if` | 1 |
| `for c in mdp` | `for` | 2 |
| `if c.isupper()` | `if` | 3 |
| `elif c.islower()` | `elif` | 4 |
| `elif c.isdigit()` | `elif` | 5 |
| `if not (majuscule and minuscule)` | `if` + `and` | 7 |
| `if not chiffre` | `if` | 8 |
| `if not special` | `if` | 9 |
| `if login and login.lower() in mdp.lower()` | `if` + `and` | 11 |
| `for i in range(len(mdp) - 2)` | `for` | 12 |
| `if mdp[i] == mdp[i + 1] == mdp[i + 2]` | `if` | 13 |
| `if not problemes` | `if` | 14 |
| `"moyen" if len(problemes) <= 2 else "faible"` | ternaire | 15 |
**M = 15 + 1 = 16.** Le `else` final de la cascade `if/elif/else` et le `break` ne comptent pas. Comme pour P3, la comparaison chaînée `mdp[i] == mdp[i + 1] == mdp[i + 2]` est comptée comme une seule condition.
P4 a le même M que la version refactorisée de `mention` vue en cours : le dictionnaire remplace la cascade de `if/elif` (qui aurait donné M = 5).