Ce cours présente la notion de qualité de code et comment il est possible de l'évaluer grâce à des métriques mesurables. Après avoir présenté plusieurs métriques standards, il se concentrer sur des aspects de qualité de code spécifique à l'orienté objet et présente les cinq concepts de l'orienté objet. La deuxième partie du cours présente plusieurs bonnes pratiques à avoir en programmation orientée objet, sur base d'exemples concrets.
1. PO3T Programmation orientée objet
Séance 12
Qualité de code et
bonnes pratiques
Sébastien Combéfis, Quentin Lurkin mercredi 9 décembre 2015
2. Ce(tte) œuvre est mise à disposition selon les termes de la Licence Creative Commons
Attribution – Pas d’Utilisation Commerciale – Pas de Modification 4.0 International.
3. Rappels
Variable et méthode de classe
Instance de la classe Class
Partage d’information entre instances
Définition et utilisation de classe interne d’instance/de classe
Partage privé de this avec une autre instance
Modularité et structuration « horizontale »
Organisation de classes en packages
Définition et constitution d’un package de classes
Visibilité package
3
4. Objectifs
Évaluer la qualité d’un code
Définition et critères de qualité d’un code
Processus de refactoring
Bonnes pratiques en programmation orientée objet
Utilisation adéquate des classes et objets
Héritage versus composition
4
7. Qualité de code (2)
Nécessite d’évaluer la qualité d’un code
Utilisation de métrique et critères d’évaluation
Plusieurs types de qualité
Qualité du code (ou structurelle)
Manière avec laquelle une fonctionnalité est implémentée
Qualité logicielle (ou fonctionnelle)
Résultat final de la fonctionnalité
Qualité du code peut être mesurée automatiquement
Outils existants pour la plupart des langages de programmation
7
8. Métrique
Métrique permet de mesurer un certain aspect du code
Fournit une valeur chiffrée pour un critère mesuré
Ne pas se forcer à atteindre de bonnes valeurs des métriques
Course aux bonnes valeurs et comportement contre-productif
Un mauvais rapport doit être perçu comme un signal d’alarme
Le lecteur du rapport doit donc aller plus loin dans sa démarche
8
9. Métriques standards (1)
Complexité cyclomatique
Nombre de chemins linéaires possibles dans une fonction
SLOC (Source Line of Code)
Nombre de lignes de code du programme
Densité des commentaires (DC)
DC = CLOC / SLOC
Couverture de code
Proportion du code qui est couverte par des tests
9
10. Métriques standards (2)
Code dupliqué
Pourcentage de code dupliqué ou très similaire
Couplage afferent (Ca) ou efferent (Ce)
Nombre de références vers/depuis une classe
Instabilité (Ce / (Ce + Ca))
Niveau de résistance au changement (stable = difficile à changer)
10
11. Couplage
La classe Shape est très stable
Ca = 2, Ce = 0 et donc une instabilité de 0
Niveau d’abstraction élevé pour des classes très stables
Rapport entre types abstraits et autres types
Shape
Rectangle
Square
Circle
11
12. Autres critères (1)
Architecture
Maintenabilité, évolutivité, performance, pertinence
Style et lisibilité
Mise en page, indentation, structure, nommage...
Documentation technique
Pour qu’une personne extérieure puisse rentrer dans le code
Fiabilité
Nombre de défaillances rencontrées dans un laps de temps donné
12
13. Autres critères (2)
Portabilité
Capacité d’un logiciel à fonctionner sur des systèmes différents
Sécurité
Exigences en matière de sécurité dépendent du type de logiciel
Nombre de bugs
Nombre de problèmes perturbant l’usage normal du logiciel
13
14. KISS
Keep It Simple, Stupid
Il faut éviter de rendre les problèmes plus compliqués
Principe phare en ingénierie logicielle
“Many great problem solvers were not great coders,
but yet they produced great code !”
14
15. Refactoring (1)
Transformation de code qui préserve son comportement
“A change made to the internal structure of a software to
make it easier to understand and cheaper to modify without
changing its observable behaviour” — Martin Fowler
Objectifs
Rendre plus facile l’ajout de nouveau code
Améliorer le design du code existant
Mieux comprendre un code
Rendre le code moins ennuyeux
15
16. Refactoring (2)
Ne faire du refactoring que sur du code fonctionnel et testé
Permet d’améliorer la qualité du code
Utilisation de cycles TDD
Utiliser des tests unitaires pour se rassurer que rien n’a été altéré
Les problèmes de design proviennent de code...
...dupliqué
...pas clair
...compliqué
16
17. Programmation orientée objet
Il faut trouver et identifier des objets du monde réel
Plusieurs questions à se poser
Identifier l’objet et ses attributs
Déterminer ce qui peut être fait avec l’objet
Identifier ce que l’objet peut faire à d’autre objets
Déterminer ce qui sera visible de l’objet
Définir l’interface publique de l’objet
17
18. Abstraction
Créer une bonne abstraction de l’objet représenté
S’assurer que les détails d’implémentation soient bien cachés
L’abstraction doit former un tout cohérent et consistent
Le niveau d’abstraction doit être consistent
Abstraction — “Le fait de voir une opération complexe
sous une forme simplifiée”
18
19. Encapsulation
Minimiser l’accès aux classes et à leurs membres
Ne pas exposer les données de la classe
Ne pas exposer des détails d’implémentation
Diminuer au maximum le couplage entre classes
1 public class GradeReport
2 {
3 private Grade [] grades;
4
5 public double getGrade (String courseName) { /* ... */ }
6
7 private static class Grade { /* ... */ }
8 }
19
20. Membres d’une classe
Limiter le nombre de méthodes
Limiter les appels de méthodes directs et indirects
En général : limiter la collaboration avec d’autres classes
Law of Demeter (LoD)
Une méthode M d’un objet O ne peut invoquer que :
1 ses propres méthodes ;
2 les méthodes de ses paramètres ;
3 les méthodes des objets qu’elle instance ;
4 et les méthodes de ses objets composants.
20
21. Constructeur
Initialiser toutes les variables d’instance
Interdire la création d’instances avec un constructeur privé
Éviter les shallow copies des paramètres
1 public class BookStore
2 {
3 private Books [] books;
4
5 public BookStore (Books [] books)
6 {
7 this.books = books;
8 }
9 }
21
22. Pourquoi une classe ?
Modéliser un objet du monde réel
Modéliser des objets abstraits
Réduire ou isoler la complexité
Limiter les impacts lors de modifications
Cacher les données et détails d’implémentation
Faciliter la réutilisation de code
22
23. Composition ou héritage ? (1)
HAS-A
Une classe se compose à
partir d’autres
7 ± 2 (composants)
Surveillez le couplage
IS-A
Une classe est une
spécialisation d’une autre
6 (niveaux d’héritage)
Surveillez l’encapsulation
23
25. Principe de Substitution de Liskov
Liskov Substitution Principle (LSP)
Si q(x) est une propriété démontrable pour tout objet x de type T,
Alors q(y) est vraie pour tout objet y de type S
tel que S est un sous-type de T.
Principe qui définit ce qu’est un bon sous-type
Soit S un sous-type de T
Tout objet de type T peut être remplacé par un objet de type S
Pas d’altération des propriétés désirables du programme
25
26. Violation du LSP
Rectangle
Square
getWidth(), setWidth(w),
getHeight(), setHeight(h)
@post width et height
librement modifiables
@post width et height
doivent être égaux
Square ne peut pas être utilisé partout à la place de Rectangle
Redéfinition des mutateurs dans Square avec vérification
Violation de la postcondition des Rectangle
26
27. Cinq principes de l’OO
1 SRP — Single Responsibility Principle
Une classe = une responsabilité (éviter les god classes)
2 OCP — Open/Closed Principle
Entité software open pour l’extension, fermée pour la modification
3 LSP — Liskov Substitution Principle
Remplacement d’un objet d’un type par un autre d’un sous-type
4 ISP — Interface Segregation Principle
Plusieurs interfaces clients plutôt qu’une seule grosse
5 DIP — Dependency Inversion Principle
Module de haut niveau doit dépendre d’abstractions
27
29. Exemple 1
Il faut éviter un bloc vide pour l’instruction catch
Impossibilité de détecter si une erreur s’est produite
1 public static void main (String [] args)
2 {
3 for (int i = 0; i < 10; i++)
4 {
5 try
6 {
7 Thread.sleep (i * 100);
8 }
9 catch ( InterruptedException exception){}
10 }
11 }
29
30. Exception
Plusieurs types d’erreurs peuvent causer une exception
Bug
Mauvaise entrée utilisateur
Un problème n’étant pas un bug
Plusieurs réactions possibles suite à une exception
Informer l’utilisateur (recommandé)
Logguer le problème
Envoyer un e-mail à l’administrateur
30
31. Exemple 2
Il ne faut pas toujours réinventer la roue
Il faut exploiter au maximum les librairies existantes
1 public static void main (String [] args)
2 {
3 String [] tab = {"One", "Two", "Three", "Four",
4 "Five", "Six", "Eleven"};
5 String s = "[";
6
7 if (tab.length > 0)
8 {
9 s += tab [0];
10 }
11
12 for (int i = 1; i < tab.length; i++)
13 {
14 s += ", " + tab[i];
15 }
16
17 System.out.println (s + "]");
18 }
31
32. Exploiter la librairie standard
Il faut exploiter la librairie standard du language
Recèle de classes avec des méthodes utiles
Il existe également des librairies spécialisées
Boost pour C++, SciPy en Python, JUNG en Java...
1 public static void main (String [] args)
2 {
3 String [] tab = {"One", "Two", "Three", "Four",
4 "Five", "Six", "Eleven"};
5
6 String s = Arrays.toString (tab);
7
8 System.out.println (s);
9 }
32
33. Exemple 3
Attention aux calculs avec des nombres en précision finie
Il faut utiliser des objets spécialisés pour ces calculs
1 public static List <Double > change (double toPay , double givenMoney)
2 {
3 ArrayList <Double > back = new ArrayList <Double >();
4 double diff = givenMoney - toPay;
5 int i = coins.length - 1;
6
7 while (diff != 0)
8 {
9 while (i >= 0 && coins[i] <= diff)
10 {
11 back.add (coins[i]);
12 diff = diff - coins[i];
13 }
14 i = i - 1;
15 }
16
17 return Collections . unmodifiableList (back);
18 }
33
34. Classe BigDecimal (1)
Utiliser java.math.BigDecimal pour représenter de l’argent
Les BigDecimal sont des objets immuables
Utiliser le style d’arrondi ROUND_HALF_EVEN
Utiliser le constructeur BigDecimal (String)
Utiliser les types primitifs int ou long
≤ 9 chiffres : int, long ou BigDecimal
≤ 18 chiffres : long ou BigDecimal
> 18 chiffres : BigDecimal
34
35. Classe BigDecimal (2)
1 public static List <BigDecimal > change ( BigDecimal toPay ,
2 BigDecimal givenMoney)
3 {
4 ArrayList <BigDecimal > back = new ArrayList <BigDecimal >();
5 BigDecimal diff = givenMoney.subtract (toPay);
6 int i = coins.length - 1;
7
8 while (diff.compareTo ( BigDecimal .ZERO) != 0)
9 {
10 while (i >= 0 && coins[i]. compareTo (diff) <= 0)
11 {
12 back.add (coins[i]);
13 diff = diff.subtract (coins[i]);
14 }
15 i = i - 1;
16 }
17
18 return Collections . unmodifiableList (back);
19 }
35
36. Exemple 4
Fermeture et libération correcte des ressources
Par exemple lorsqu’on ouvre un fichier, ou un socket...
1 try
2 {
3 List <String > list = Arrays.asList ("1", "2", "3");
4 BufferedWriter writer = new BufferedWriter (new FileWriter ("file.txt"));
5
6 for (String data : list)
7 {
8 writer.write (data);
9 writer.newLine ();
10 }
11
12 writer.close ();
13 }
14 catch ( IOException exception)
15 {
16 System.err.println ("Erreur : " + exception.getMessage ());
17 }
36
37. Utilisation de finally
Instruction finally exécutée dans tous les cas
Créer une méthode et laisser remonter les erreurs d’E/S
1 public static void print (List <String > list , String path) throws IOException
2 {
3 try
4 {
5 List <String > list = Arrays.asList ("1", "2", "3");
6 BufferedWriter writer = new BufferedWriter (new FileWriter (path));
7
8 for (String data : list)
9 {
10 writer.write (data);
11 writer.newLine ();
12 }
13 }
14 finally
15 {
16 writer.close ();
17 }
18 }
37
38. Exemple 5
Énumérations pour un ensemble de valeurs possibles
Pour que le compilateur puisse contrôler les valeurs
1 public class VendingMachine
2 {
3 public static final double ONECENT = 0.01;
4 public static final double TWOCENTS = 0.02;
5 // ...
6
7 public void insert (double coin)
8 {
9 // ...
10 }
11
12 public static void main ( String [] args)
13 {
14 new VendingMachine ().insert (TWOCENTS);
15 }
16 }
38
40. Exemple 6
Prudence en utilisant l’héritage
Rédéfinir adéquatement les méthodes, songer à la composition
1 public class Point
2 {
3 private final int x, y;
4
5 public Point (int x, int y)
6 {
7 this.x = x;
8 this.y = y;
9 }
10 }
11
12 public class ColoredPoint extends Point
13 {
14 private final Color color;
15
16 public ColoredPoint (int x, int y, Color c)
17 {
18 super (x, y);
19 color = c;
20 }
21 }
40
41. Héritage
Il faut utiliser l’héritage avec prudence
Attention à la redéfinition de equals, compareTo...
Les constructeurs, readObject et clone ne devraient pas
appeler des méthodes pouvant être redéfinies
Empêcher la redéfinition avec final
Considérer la composition à la place de l’héritage
41