Erwan Bousse
Université de Nantes
Vérification statique
Erwan Bousse
Université de Nantes
Dernière révision : 27/01/2024, 09:38
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
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
Définition, objectifs
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
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.
Aucune utilisation de méthode ou classe déclarées comme @deprecated
.
Toujours utiliser des accolades à l’intérieur des boucles et des conditionnelles.
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.
Transformer des programmes en chiffres.
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.
1
2
3
4
5
if a
then
i1
else
i2
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
}
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;
}
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);
}
}
Quelle(s) métrique(s) pour mesurer la complexité d’un code source ?
Plus il y a de chemins possibles dans un programme, plus il est difficile à appréhender, donc plus il est complexe.
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.
Nombre maximal de chemins linéairement indépendants pour parcourir un programme depuis son entrée vers sa sortie.
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
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
}
On a \$N=7\$ et \$E=8\$, donc \$M = 8 - 7 + 2 = 3\$.
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
}
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.
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;
}
On a \$N=8\$ et \$E=9\$, donc \$M = 9 - 8 + 2 = 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);
}
}
On a \$N=17\$ et \$E=21\$, donc \$M = 21 - 17 + 2 = 6\$.
Nombre total de chemins possibles au sein d’un programme, sans compter les chemins qui passent plusieurs fois par une boucle.
Pas de formule toute prête ! On compte les chemins un à un sur le CFG.
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
}
4 chemins possibles entre E et S, donc NPath de 4 :
|
|
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;
}
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. |
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);
}
}
É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.
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.
Mesurer la quantité et la proportion de couplage dans un logiciel. Deux métriques :
Coupling Between Object classes (CBO)
Coupling Factor (CF)
Le CBO d’une classe C est égal au nombre de classes couplées avec C.
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,
6 couplages :
| Mesures CBO :
|
Mesure CF : \$\text{CF}(C1,C2,C3,C4,C5) = \frac {2 \times 6 \times (5 - 2)! } {5!} = 60\% \$
5 couplages :
| Mesures CBO :
|
Mesure CF : \$"CF"(C1,C2,C3,C4,C5, C6) = frac {2 × 5 × (6 - 2)! } {6!} = 33.33% \$
Pouvoir définir ce que sont des programmes bien écrits.
Faire relire le code par des humains.
Faire relire le code par des logiciels.