Vérification et Validation (partie 2)

Vérification statique

Erwan Bousse

Université de Nantes

Dernière révision : 27/01/2024, 09:38

Contenu de cette partie (1)

  • Présentation de la vérification statique,

  • Notion d'exigence générique,

  • Notion de métrique logicielle,

  • Pré-requis 1 : notion de graphe de flot de contrôle (CFG)

  • Présentation de métriques :

    • Complexité cyclomatique,

    • NPath,

    • Couplage.

  • Exercices TD (CFG + métriques)

  • Notion de règles de codage

Contenu de cette partie (2)

  • Approche par revue de code,

  • Exercices de TD (revue de code)

  • Pré-requis 2 : arbres syntaxiques abstraits (AST)

  • Approche par analyse statique

  • Présentation de PMD

  • Exercices de TD

Vérification statique ?

Définition, objectifs

Vérification statique (1)

statique.drawio

Vérification statique (2)

Exigences génériques : questions principales
  • Est-ce que le logiciel est susceptible de crasher (par ex. NullPointerException) lors de certains de ses comportements ? − exigences fonctionnelles

  • Est-ce que le code source du logiciel est de bonne qualité (lisible, maintenable, pas trop complexe, etc.) ? − exigences non-fonctionnelles

Méthodes
  • revues de code: un humain relit le code à la recherche de défauts,

  • analyse statique: un logiciel explore le code automatiquement à la recherche de défauts.

Exigences génériques et statiques

Exemple d’exigences génériques et statiques

Exemple 1. Des exigences génériques statiques pour la famille des programmes Java
  • Aucune utilisation de méthode ou classe déclarées comme @deprecated.

  • Toujours utiliser des accolades à l’intérieur des boucles et des conditionnelles.

Exemple 2. Des exigences génériques statiques pour la famille des programmes orientés objet
  • Toute variable, attribut ou paramètre doit être typé par une interface abstraite et non par une classe (ex. écrire List<Integer> list au lieu de ArrayList<Integer> list).

  • Une classe ne doit pas contenir plus de 10 attributs.

Métriques logicielles

Transformer des programmes en chiffres.

Métriques logicielles

metrique.drawio

Pré-requis : Graphe de flot de controle (CFG)

Un graphe de flot de contrôle (en anglais, control flow graph, ou CFG) modélise l’ensemble des chemins potentiels qui existent au sein d’un programme, afin de pouvoir formaliser des métriques ou des critères basés sur ces chemins.

Un CFG est composé :

  • d’un ensemble de sommets, chaque sommet pouvant représenter au choix :

    • l'entrée du programme nommée E,

    • la sortie du programme nommée S,

    • un bloc élémentaire du programme,

  • et d’un ensemble d'arcs pouvant représenter au choix :

    • une prise de décision,

    • le passage automatique d’un bloc élémentaire à un autre.

Exemple de CFG (1)

1
2
3
4
5
if a
then  
    i1
else 
    i2
basic cfg.drawio

Exemple de CFG (2)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void function(int x) {
    if (x < -1)
        x = -x;
    else
    fi
    if (x == -1)
        x = 1;
    else
        x = x + 1;
    fi
}
fonction cfg.drawio

Exemple de CFG (3)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
/* PGCD de 2 nombres
   Précondition: p et q sont des entiers positifs*/

public int pgcd() {
    int p;
    int q;
    p = read(); q = read();
    while (p != q) {
        if (p > q) {
            p = p - q;
        } else {
            q = q - p;
        }
    }
    return p;
}
pgcd cfg.drawio

Exemple de CFG (4)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void stringMatch(String one, String two) {
    boolean match = false;
    if (one.charAt(0) == two.charAt(0)) {
        System.out.println(match = true);   // returns true
    } else {
        System.out.println(match);       // returns false
    }
    for (int i = 0; i < two.length(); i++) {  
        int temp = i;
        for (int x = 0; x < one.length(); x++) {
            if (two.charAt(temp) == one.charAt(x)) {
                System.out.println(match = true);    //returns true
                i = two.length();
            }
        }
    }
    int num1 = one.length() - 1;
    int num2 = two.length() - 1;
    if (one.charAt(num1) == two.charAt(num2)) {
        System.out.println(match = true);
    } else {
        System.out.println(match = false);
    }
}
stringMatch cfg.drawio

Métriques de compléxité

Question

Quelle(s) métrique(s) pour mesurer la complexité d’un code source ?

Intuition

Plus il y a de chemins possibles dans un programme, plus il est difficile à appréhender, donc plus il est complexe.

Idée

Utiliser le CFG d’un programme pour analyser les chemins possibles et en mesurer la complexité. Deux métriques de complexité : Complexité cyclomatique et NPath.

Complexité cyclomatique

Définition − Comparaison Cyclomatique

Nombre maximal de chemins linéairement indépendants pour parcourir un programme depuis son entrée vers sa sortie.

Formule
\$M = E – N + 2\$

avec :

  • \$E\$ le nombre d’arcs du CFG du programme, et

  • \$N\$ le nombre de nœuds du CFG du programme.

Définition alternative : nombre de prises de décisions dans un programme + 1

Exemple complexité cyclomatique (1)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void function(int x) {
    if (x < -1)
        x = -x;
    else
    fi
    if (x == -1)
        x = 1;
    else
        x = x + 1;
    fi
}
fonction cfg.drawio

On a \$N=7\$ et \$E=8\$, donc \$M = 8 - 7 + 2 = 3\$.

Exemple complexité cyclomatique (1) − visualisation des chemins

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void function(int x) {
    if (x < -1)
        x = -x;
    else
    fi
    if (x == -1)
        x = 1;
    else
        x = x + 1;
    fi
}
fonction cfg paths

On trace le chemin jaune, puis le chemin violet ajoute l’arc 6 → 8−9 par rapport au jaune, puis le chemin rouge ajoute l’arc 2 → 6 par rapport aux deux autres chemins.

Exemple complexité cyclomatique (2)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
/* PGCD de 2 nombres
   Précondition: p et q sont des entiers positifs*/

public int pgcd() {
    int p;
    int q;
    p = read(); q = read();
    while (p != q) {
        if (p > q) {
            p = p - q;
        } else {
            q = q - p;
        }
    }
    return p;
}
pgcd cfg.drawio

On a \$N=8\$ et \$E=9\$, donc \$M = 9 - 8 + 2 = 3\$.

Exemple complexité cyclomatique (3)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void stringMatch(String one, String two) {
    boolean match = false;
    if (one.charAt(0) == two.charAt(0)) {
        System.out.println(match = true);   // returns true
    } else {
        System.out.println(match);       // returns false
    }
    for (int i = 0; i < two.length(); i++) {  
        int temp = i;
        for (int x = 0; x < one.length(); x++) {
            if (two.charAt(temp) == one.charAt(x)) {
                System.out.println(match = true);    //returns true
                i = two.length();
            }
        }
    }
    int num1 = one.length() - 1;
    int num2 = two.length() - 1;
    if (one.charAt(num1) == two.charAt(num2)) {
        System.out.println(match = true);
    } else {
        System.out.println(match = false);
    }
}
stringMatch cfg.drawio

On a \$N=17\$ et \$E=21\$, donc \$M = 21 - 17 + 2 = 6\$.

NPath

Définition − NPath

Nombre total de chemins possibles au sein d’un programme, sans compter les chemins qui passent plusieurs fois par une boucle.

Méthode

Pas de formule toute prête ! On compte les chemins un à un sur le CFG.

Exemple NPath (1)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void function(int x) {
    if (x < -1)
        x = -x;
    else
    fi
    if (x == -1)
        x = 1;
    else
        x = x + 1;
    fi
}
fonction cfg.drawio

4 chemins possibles entre E et S, donc NPath de 4 :

  • E, 2, 3, 6, 7, S.

  • E, 2, 3, 6, 8−9, S.

  • E, 2, 6, 7, S.

  • E, 2, 6, 8-9, S.

Exemple NPath (2)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
/* PGCD de 2 nombres
   Précondition: p et q sont des entiers positifs*/

public int pgcd() {
    int p;
    int q;
    p = read(); q = read();
    while (p != q) {
        if (p > q) {
            p = p - q;
        } else {
            q = q - p;
        }
    }
    return p;
}
pgcd cfg.drawio

3 chemins possibles, le NPath est donc de 3.

E, 5−7, 8, 9, 10, 8, 14, S.

E, 5−7, 8, 9, 12, 8, 14, S.

E, 5−7, 8, 14, S.

Exemple NPath (3)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void stringMatch(String one, String two) {
    boolean match = false;
    if (one.charAt(0) == two.charAt(0)) {
        System.out.println(match = true);   // returns true
    } else {
        System.out.println(match);       // returns false
    }
    for (int i = 0; i < two.length(); i++) {  
        int temp = i;
        for (int x = 0; x < one.length(); x++) {
            if (two.charAt(temp) == one.charAt(x)) {
                System.out.println(match = true);    //returns true
                i = two.length();
            }
        }
    }
    int num1 = one.length() - 1;
    int num2 = two.length() - 1;
    if (one.charAt(num1) == two.charAt(num2)) {
        System.out.println(match = true);
    } else {
        System.out.println(match = false);
    }
}
stringMatch cfg.drawio

Exemple NPath (3) − suite

Énumérons tous les chemins entre E et S :

  • E, 2−3, 4, 8-init, 8-cond, 9, 10-init, 10-cond, 11, 12−13, 10-incr, 10-cond, 8-incr, 8-cond, 17−19, 20, S.

  • E, 2−3, 4, 8-init, 8-cond, 9, 10-init, 10-cond, 11, 12−13, 10-incr, 10-cond, 8-incr, 8-cond, 17−19, 21, S.

  • E, 2−3, 4, 8-init, 8-cond, 9, 10-init, 10-cond, 11, 10-incr, 10-cond, 8-incr, 8-cond, 17−19, 20, S.

  • E, 2−3, 4, 8-init, 8-cond, 9, 10-init, 10-cond, 11, 10-incr, 10-cond, 8-incr, 8-cond, 17−19, 21, S.

  • E, 2−3, 4, 8-init, 8-cond, 9, 10-init, 10-cond, 8-incr, 8-cond, 17−19, 20, S.

  • E, 2−3, 4, 8-init, 8-cond, 9, 10-init, 10-cond, 8-incr, 8-cond, 17−19, 21, S.

  • E, 2−3, 4, 8-init, 8-cond, 17−19, 20, S.

  • E, 2−3, 4, 8-init, 8-cond, 17−19, 21, S.

  • E, 2−3, 6, 8-init, 8-cond, 9, 10-init, 10-cond, 11, 12−13, 10-incr, 10-cond, 8-incr, 8-cond, 17−19, 20, S.

  • E, 2−3, 6, 8-init, 8-cond, 9, 10-init, 10-cond, 11, 12−13, 10-incr, 10-cond, 8-incr, 8-cond, 17−19, 21, S.

  • E, 2−3, 6, 8-init, 8-cond, 9, 10-init, 10-cond, 11, 10-incr, 10-cond, 8-incr, 8-cond, 17−19, 20, S.

  • E, 2−3, 6, 8-init, 8-cond, 9, 10-init, 10-cond, 11, 10-incr, 10-cond, 8-incr, 8-cond, 17−19, 21, S.

  • E, 2−3, 6, 8-init, 8-cond, 9, 10-init, 10-cond, 8-incr, 8-cond, 17−19, 20, S.

  • E, 2−3, 6, 8-init, 8-cond, 9, 10-init, 10-cond, 8-incr, 8-cond, 17−19, 21, S.

  • E, 2−3, 6, 8-init, 8-cond, 17−19, 20, S.

  • E, 2−3, 6, 8-init, 8-cond, 17−19, 21, S.

Sauf erreur, on a 16 chemins possibles, le NPath de ce programme est donc 16.

Comparaison Cyclomatique / NPath

cc npath

Métriques de couplage

Question

Quelle(s) métrique(s) pour mesurer la quantité de couplage dans un logiciel ?

Rappel : deux composants logiciel sont couplés s’il existe une dépendance de l’un envers l’autre, dans un sens ou dans l’autre.

Idée

Mesurer la quantité et la proportion de couplage dans un logiciel. Deux métriques :

  • Coupling Between Object classes (CBO)

  • Coupling Factor (CF)

Métriques CBO et CF

Définition − Coupling Between Object classes (CBO)

Le CBO d’une classe C est égal au nombre de classes couplées avec C.

Définition − Coupling Factor (CF)

Le CF d’un ensemble de classes est le pourcentage de couplages, autrement dit le rapport entre le nombre couplages réels et le nombre de couplages possibles

\$"CF" = frac{X}{((NbC),(2))}\$

ou

\$"CF" = frac {2X*(NbC-2)! }{ NbC! }\$
  • \$X\$ le nombre total de couplages,

  • \$NbC\$ le nombre total de classes,

Exemple de couplage (1)

coupling1

6 couplages :

  • \$(C1, C2)\$, \$(C1, C3)\$, \$(C1, C4)\$

  • \$(C3, C4)\$, \$(C2, C4)\$, \$(C4, C5)\$

Mesures CBO :

  • \$\text{CBO}(C1) = 3\$ ; \$\text{CBO}(C2) = 2\$

  • \$\text{CBO}(C3) = 2\$ ; \$\text{CBO}(C4) = 3\$

  • \$\text{CBO}(C5) = 1\$

Mesure CF : \$\text{CF}(C1,C2,C3,C4,C5) = \frac {2 \times 6 \times (5 - 2)! } {5!} = 60\% \$

Exemple de couplage (2)

coupling2

5 couplages :

  • \$(C1, C2)\$, \$(C2, C3)\$, \$(C3, C4)\$

  • \$(C3, C6)\$, \$(C4, C5)\$

Mesures CBO :

  • \$"CBO"(C1) = 1\$ ; \$"CBO"(C2) = 1\$

  • \$"CBO"(C3) = 2\$ ; \$"CBO"(C4) = 2\$

  • \$"CBO"(C5) = 1\$ ; \$"CBO"(C6) = 1\$

Mesure CF : \$"CF"(C1,C2,C3,C4,C5, C6) = frac {2 × 5 × (6 - 2)! } {6!} = 33.33% \$

Règles de codage

Pouvoir définir ce que sont des programmes bien écrits.

Régles de codage

Exemple : ArgoUML

Exemple : Google Code Conventions (1)

Exemple : Google Code Conventions (2)

Exemple : Google Code Conventions (3)

Vérification statique manuelle : revue de code

Faire relire le code par des humains.

Vérification statique automatisée : analyse statique

Faire relire le code par des logiciels.