Examen de génie logiciel avancé : 3. Qualité logicielle et refactoring

Durée : 2 heures · Barème : sur 20 points

Documents : Aucun document autorisé. Calculatrice simple admise.

Cours associé : chapitre 3

Sujet

Partie 1 : Questionnaire à choix multiple

10 questions · 5 points · une seule réponse exacte par question

  1. La complexité cyclomatique d'un graphe de flot se calcule par :
    1. V(G) = N − E + 2
    2. V(G) = E − N + 2
    3. V(G) = E + N − 2
    4. V(G) = E − N − 2
  2. Dans une condition composée, si a et b alors compte pour :
    1. un point de décision
    2. deux points de décision
    3. aucun point de décision
    4. trois points de décision
  3. Un module à cohésion faible se reconnaît à :
    1. son grand nombre de dépendances entrantes
    2. le fait que ce qu'il contient n'a pas de rapport commun
    3. sa complexité cyclomatique élevée
    4. son absence de tests unitaires
  4. Selon la définition de Fowler, un refactoring :
    1. améliore les performances du programme
    2. corrige les défauts détectés en revue
    3. modifie la structure interne sans changer le comportement observable
    4. ajoute les tests manquants
  5. Avant de refactorer une fonction non couverte par des tests, il faut :
    1. demander l'accord du comité de pilotage
    2. écrire des tests de caractérisation
    3. mesurer sa complexité cyclomatique
    4. documenter son comportement attendu
  6. Ajouter un cas sans modifier le code existant est la promesse du principe :
    1. de responsabilité unique
    2. ouvert, fermé
    3. de substitution de Liskov
    4. d'inversion des dépendances
  7. Dans le quadrant de Fowler, « on livre pour le salon et on reprend la couche d'accès aux données en janvier » est une dette :
    1. délibérée et imprudente
    2. délibérée et prudente
    3. involontaire et prudente
    4. involontaire et imprudente
  8. La transformation qui répond à l'odeur « conditionnel sur le type » est :
    1. extraire une fonction
    2. introduire un objet paramètre
    3. remplacer le conditionnel par le polymorphisme
    4. déplacer la méthode
  9. Une porte de qualité a le plus de chances de tenir dans le temps si ses seuils portent sur :
    1. la totalité du dépôt
    2. le code nouveau ou modifié
    3. les seuls fichiers critiques
    4. la moyenne des trois derniers mois
  10. Le ratio de dette technique produit par un outil d'analyse statique rapporte :
    1. la dette au chiffre d'affaires du projet
    2. le coût de remise en état au coût de réécriture complète
    3. le nombre de défauts au nombre de lignes
    4. la dette nouvelle à la dette remboursée

Partie 2 : Exercices

Exercice 1 · 4 points

Compter les chemins d'une fonction de contrôle

Cette fonction valide une demande d'intervention avant son enregistrement.

fonction valider(d)
  si d.client = vide ou d.site = vide alors
    retourner "client ou site manquant"
  finsi
  si d.urgence < 1 ou d.urgence > 4 alors
    retourner "urgence hors bornes"
  finsi
  si d.urgence = 4 et d.responsable = vide alors
    retourner "une urgence 4 exige un responsable"
  finsi
  pour chaque piece dans d.pieces faire
    si piece.quantite <= 0 alors
      retourner "quantite invalide"
    finsi
  finpour
  retourner "ok"
fin
  1. Calculez V(G). Présentez votre décompte point de décision par point de décision.
  2. Combien de cas de test au minimum pour couvrir toutes les branches ? Justifiez par la mesure.
  3. Proposez le jeu de cas de test correspondant, sous forme de tableau donnant l'entrée et le résultat attendu.
  4. Cette fonction doit-elle être découpée ? Répondez en vous appuyant sur les seuils usuels, et nuancez.

Exercice 2 · 4 points

Nommer les odeurs et la transformation qui répond

Les quatre situations suivantes sont relevées lors d'une revue de code. Pour chacune, nommez l'odeur de conception, la transformation de refactoring qui y répond, et le bénéfice attendu. Une réponse en trois lignes suffit.

  1. La méthode Facture.calculerTotal() appelle onze fois des données de l'objet Client qu'elle reçoit en paramètre, et deux fois seulement les siennes.
  2. Six méthodes du projet reçoivent toutes la même suite de paramètres (ville, quartier, rue, porte, codePostal).
  3. Dans InterventionService, un selon sur le champ statut apparaît dans quatre méthodes différentes, avec les mêmes cinq valeurs à chaque fois.
  4. La classe GestionnaireIntervention compte 1 400 lignes, dépend de huit autres modules, et a été modifiée dans trente et un des quarante derniers tickets.

Partie 3 : Problème

7 points

NKILItech : reprendre le module de gestion des interventions

Tableau de bord de qualite du module de gestion des interventions chez NKILItech : repartition des 186 fonctions par tranche de complexite cyclomatique, taux de duplication de 11,3 pour cent, couverture de 18 pour cent, dette estimee a 62 jours et reecriture a 410 jours.

Le module de gestion des interventions de NKILItech est en service depuis sept ans. L'équipe y consacre 38 % de son temps à de la maintenance corrective, et chaque demande d'évolution est annoncée à trois semaines avant d'en prendre six. La direction technique demande un diagnostic et un plan. Le tableau de bord ci-dessus résume l'analyse statique.

  1. Combien de fonctions dépassent le seuil du raisonnable ? Donnez le nombre et la part du total, et dites pourquoi cette part est plus parlante que la complexité moyenne du module.
  2. Calculez le ratio de dette technique. Interprétez-le : que dit-il, et que ne dit-il pas ?
  3. Un architecte propose de réécrire le module. Prenez position, avec trois arguments appuyés sur les chiffres du tableau de bord.
  4. Proposez un plan de reprise en quatre étapes ordonnées. Pour chacune, indiquez ce qu'on fait et comment on constate que l'étape est terminée.
  5. Rédigez la porte de qualité que vous poseriez, avec des seuils chiffrés. Justifiez le périmètre sur lequel vous la posez.
  6. Les six fonctions les plus complexes ont une complexité cyclomatique moyenne de 27. Combien de cas de test faut-il au minimum pour les couvrir toutes ? Cette estimation est-elle un plancher ou un plafond ?

Correction

Cherchez avant d'ouvrir. Les corrigés détaillent le raisonnement attendu et le barème.

Corrigé du QCMMasquer

1. Réponse b : V(G) = E − N + 2
E est le nombre d'arcs, N le nombre de nœuds. On retient la formule par son résultat : un programme sans aucune décision forme une chaîne où E = N − 1, ce qui donne bien V(G) = 1.

2. Réponse b : deux points de décision
Chaque opérateur logique ajoute un point de décision, car il crée une branche supplémentaire dans le graphe de flot : on peut sortir parce que a est faux, ou parce que b l'est. C'est ce qui rend les conditions composées coûteuses à tester.

3. Réponse b : le fait que ce qu'il contient n'a pas de rapport commun
La cohésion décrit ce qui se passe à l'intérieur du module : ses éléments vont-ils ensemble ? Le nombre de dépendances est le couplage, qui est une autre mesure. Un module nommé Utils est le cas d'école de la cohésion faible.

4. Réponse c : modifie la structure interne sans changer le comportement observable
La définition est volontairement stricte. Si le comportement observable change, l'opération est une évolution ou une correction, et elle se conduit autrement : avec une recette, et non avec la seule exécution des tests existants.

5. Réponse b : écrire des tests de caractérisation
Les tests de caractérisation figent le comportement réel, y compris ses défauts, pour qu'on puisse affirmer ensuite que la restructuration n'a rien changé. Documenter le comportement attendu ne protège de rien, puisque le code fait peut-être autre chose.

6. Réponse b : ouvert, fermé
Ouvert à l'extension, fermé à la modification. C'est ce que permet le polymorphisme là où une cascade de selon obligerait à rouvrir, et donc à risquer, du code qui fonctionne déjà.

7. Réponse b : délibérée et prudente
Elle est délibérée parce qu'on la choisit, et prudente parce qu'on sait ce qu'on emprunte et quand on remboursera. C'est le seul cas où la dette est un bon outil de gestion. Elle devient imprudente le jour où janvier passe sans la reprise.

8. Réponse c : remplacer le conditionnel par le polymorphisme
Un champ qui pilote le comportement joue le rôle d'un type ; la réponse est de le rendre réellement typé. Extraire une fonction déplacerait le problème sans le supprimer : la cascade existerait toujours, ailleurs.

9. Réponse b : le code nouveau ou modifié
Un seuil posé sur l'existant est inatteignable sur une base ancienne, et il sera désactivé dans le mois. Posé sur le code nouveau, il est tenable dès le premier jour, et il suffit : la qualité remonte au rythme des modifications.

10. Réponse b : le coût de remise en état au coût de réécriture complète
C'est ce rapport qui rend le chiffre comparable d'un projet à l'autre, et interprétable : un ratio de 5 % se rembourse, un ratio de 60 % pose sérieusement la question de la réécriture.

Corrigé de l'exercice 1Masquer

1. Décompte. On part de 1, puis on ajoute un par point de décision :

  • d.client = vide : 1
  • ou d.site = vide : 1
  • d.urgence < 1 : 1
  • ou d.urgence > 4 : 1
  • d.urgence = 4 : 1
  • et d.responsable = vide : 1
  • pour chaque : 1
  • piece.quantite <= 0 : 1

Huit points de décision, dont quatre apportés par les opérateurs logiques. V(G) = 1 + 8 = 9.

2. Nombre minimal de cas. La complexité cyclomatique majore le nombre de chemins indépendants, donc neuf cas de test suffisent et sont nécessaires pour couvrir toutes les branches. On accordera le point à qui répond neuf en citant la mesure ; répondre « autant que de retourner », soit cinq, couvre les sorties mais non les branches, et laisse par exemple d.site = vide jamais exercé.

3. Jeu de cas.

N°EntréeRésultat attenduBranche exercée
1client videclient ou site manquantpremier terme du ou
2client présent, site videclient ou site manquantsecond terme du ou
3urgence = 0urgence hors bornesborne basse
4urgence = 5urgence hors bornesborne haute
5urgence = 4, responsable videune urgence 4 exige un responsableles deux termes du et
6urgence = 4, responsable renseignéoksecond terme du et, faux
7urgence = 2, aucune pièceokboucle non entrée
8urgence = 2, une pièce de quantité 0quantite invalidecorps de boucle, cas fautif
9urgence = 2, deux pièces validesokboucle parcourue sans sortie

Le cas 7 mérite une mention : une demande sans aucune pièce est acceptée. C'est peut-être voulu, peut-être non. Un jeu de test construit sur les branches fait apparaître la question, ce qu'une lecture rapide du code ne fait pas.

4. Faut-il découper ? V(G) = 9 se situe dans la zone « raisonnable », en dessous du seuil de 11. La réponse attendue est non, pas pour la complexité, et la nuance est le point important : cette fonction est plate, chaque contrôle est indépendant du suivant, et elle se lit de haut en bas sans effort. Le chiffre ouvre la discussion, il ne la tranche pas.

On pourra signaler un autre reproche, qui n'est pas une affaire de complexité : la fonction renvoie des messages d'erreur en clair, donc elle mélange la validation et la présentation. Renvoyer un code d'erreur, et laisser l'appelant choisir le message, rendrait la règle réutilisable et traduisible.

Corrigé de l'exercice 2Masquer

1. Envie de fonctionnalité. La méthode s'intéresse plus aux données d'autrui qu'aux siennes. La transformation est déplacer la méthode vers Client, ou extraire d'abord la part qui concerne le client si le reste doit demeurer dans Facture. Le bénéfice est la baisse du couplage : Facture cesse de connaître la structure interne de Client, qui peut alors évoluer seul.

2. Groupe de données, et liste de paramètres longue. Ces cinq valeurs voyagent toujours ensemble, ce qui est la définition d'un concept qui n'a pas encore de nom. La transformation est introduire un objet paramètre, ici une Adresse. Le bénéfice est double : les six signatures se réduisent à un paramètre, et l'objet devient le seul endroit où écrire la validation d'une adresse, aujourd'hui recopiée ou absente.

3. Conditionnel sur le type, répété. C'est l'odeur la plus coûteuse des quatre, parce qu'elle se paie à chaque ajout de statut : il faut retrouver les quatre selon, et en oublier un ne produit aucune erreur de compilation. La transformation est remplacer le conditionnel par le polymorphisme, en faisant du statut un type porteur de son comportement. Le bénéfice est la conformité au principe ouvert, fermé : un statut nouveau devient une classe nouvelle, et le compilateur exige ses méthodes.

4. Classe tentaculaire. Les trois indices se renforcent : la taille, le couplage sortant, et surtout la fréquence de modification, qui est la mesure la plus parlante. Une classe modifiée dans 78 % des tickets n'a pas une raison de changer mais vingt, donc elle viole la responsabilité unique. La transformation est extraire une classe, une par responsabilité, en se guidant sur les tickets eux-mêmes : regrouper ceux qui touchent aux mêmes lignes révèle les axes de découpe. Le bénéfice est la fin du goulot : deux demandes sans rapport cessent de se gêner dans le même fichier.

Corrigé du problèmeMasquer

1. Les fonctions au-dessus du seuil. La zone « difficile à suivre » commence à 11. On additionne donc les deux dernières tranches :

18 + 6 = 24 fonctions sur 186, soit 12,9 % du module.

Cette part est plus parlante que la moyenne parce que la moyenne est écrasée par la masse des fonctions simples. Ici, 120 fonctions sur 186 sont à 5 ou moins : la complexité moyenne du module serait rassurante et n'apprendrait rien, alors que la souffrance de l'équipe se concentre dans les 24 fonctions que personne n'ose toucher. On mesure la qualité interne par sa queue de distribution, non par son centre.

2. Le ratio de dette.

62 ÷ 410 = 0,151, soit un ratio de 15,1 %.

Il dit que remettre le module en état coûterait un peu moins d'un sixième de ce que coûterait sa réécriture. C'est élevé, et c'est largement remboursable : l'ordre de grandeur plaide pour la reprise.

Il ne dit en revanche rien de trois choses, et les énoncer vaut autant que le calcul. Il ignore d'abord la valeur métier : une dette de 62 jours sur un module qu'on arrête l'an prochain ne se rembourse pas. Il ignore ensuite la répartition : ces 62 jours peuvent tenir dans 24 fonctions ou être étalés sur 186. Il ignore enfin le risque : une couverture de 18 % rend chaque jour de remise en état plus dangereux qu'il n'en a l'air, puisque rien ne signale la régression.

3. Réécriture ou reprise ? La position attendue est la reprise progressive, et le barème récompense les arguments chiffrés, non la conclusion seule.

  • Le ratio le dit. 15,1 % signifie que la réécriture coûte près de sept fois la remise en état. On ne finance pas sept pour obtenir un.
  • Le module n'est pas uniformément malade. 120 fonctions sur 186 sont saines. Réécrire, c'est jeter les deux tiers d'un travail qui fonctionne pour atteindre 13 % de code difficile.
  • La couverture de 18 % condamne la réécriture. Sans tests, rien ne décrit le comportement attendu ; la spécification du module, ce sont sept ans de cas particuliers inscrits dans son code. Une réécriture les redécouvrirait un par un, en production. C'est l'argument décisif, et c'est aussi pourquoi la première étape du plan porte sur les tests.

On acceptera la position inverse si elle est défendue par des éléments absents du tableau de bord et posés en hypothèse : technologie en fin de support, impossibilité de recruter sur ce langage, changement de modèle métier rendant le périmètre obsolète. C'est la qualité de l'argumentation qui est notée.

4. Plan de reprise en quatre étapes.

ÉtapeCe qu'on faitComment on constate la fin
1. FiletTests de caractérisation sur les 24 fonctions au-dessus du seuil, en priorité les 6 au-dessus de 20Ces 24 fonctions sont couvertes à 80 % au moins, et la suite tourne dans la chaîne d'intégration
2. DécoupeExtraction de fonctions dans les 6 fonctions les plus complexes, par petits pas, tests exécutés à chaque pasAucune fonction du module au-dessus de 20, et les tests de l'étape 1 passent sans modification
3. DuplicationTraitement des blocs dupliqués, en commençant par ceux qui touchent au calcul des montantsDuplication sous 5 %
4. TenuePorte de qualité en vigueur, règle du boy-scout, revue systématiqueTrois mois consécutifs sans franchissement de seuil sur le code nouveau

L'ordre compte et il est noté : le filet avant la découpe. Un plan qui commence par l'extraction de fonctions sur du code non testé est un plan qui parie, et il perdra le point.

5. La porte de qualité. Sur le code nouveau ou modifié uniquement :

  • couverture des lignes nouvelles ou modifiées : 80 % au minimum ;
  • duplication introduite : aucune ;
  • aucune fonction nouvelle au-dessus de V(G) = 10 ;
  • aucune vulnérabilité ni aucun défaut bloquant ajouté.

Le périmètre est le point noté. Posée sur la totalité du dépôt, une exigence de 80 % de couverture est inatteignable à partir de 18 %, donc elle sera contournée ou désactivée avant la fin du trimestre. Posée sur le code touché, elle est tenable dès la première demande, et elle fait remonter la qualité exactement là où l'équipe travaille. C'est la règle du cliquet : on n'empêche pas la dette existante, on empêche la dette nouvelle.

6. Les cas de test des six fonctions.

6 × 27 = 162 cas de test au minimum.

C'est un plancher, et c'est le sens de la question. La complexité cyclomatique donne le nombre de chemins indépendants, donc le minimum pour couvrir les branches ; elle ne couvre ni les valeurs limites de chaque seuil, ni les combinaisons de données, ni les cas d'erreur d'infrastructure. En pratique, on dépassera largement ce chiffre.

Et ce chiffre est aussi le meilleur argument de l'étape 2 du plan : 162 cas pour six fonctions est le prix du filet si on les laisse telles quelles. Après découpe, les mêmes branches se répartissent sur des unités de V(G) = 3 ou 4, chacune testable en quelques cas lisibles, et le total nécessaire ne bouge presque pas pendant que l'effort d'écriture, lui, s'effondre.

Questions / Réponses

Aucune question. Soyez le premier à poser une question.
★ ★ ★ ★ ★
Aucune note. Soyez le premier à attribuer une note !

Ajouter un commentaire

Anti-spam