• Aucun résultat trouvé

Exemples de métriques

Chapitre 5 EVOLUTION DU PROJET EM2

5.3. l Implantation du noyau

8.1.2 Exemples de métriques

Dans la présentation qui suit, la notion de module doit être prise dans un sens large, incluant les procédures, par exemple.

Matrices de dépendance complète et du premier ordre

Myers [Mye 75] s'est intéressé à la mesure de la stabilité d'un programme. Pour cela, il a défini deux matrices: la matrice de dépendance complète et la matrice de dépendance du premier ordre. Ces mesures sont basées sur l'indépendance des modules entre eux, sur leur couplage etleur cohésion.

La matrice de dépendance complète (figure 8.1) décrit la probabilité Mij d'avoir à modifier un module i si le module j doit être modifié. Dans notre exemple de la figure 8.1, si le module C est modifié, alors il y a 40% de chances pour que Je module A doive l'être aussi.

A partir de cette matrice, on peut dériver une mesure de complexité en sommant tous les éléments Mij et en divisant par la dimension N de la matrice. Cette valeur indique le nombre moyen de modules qui doivent être changés lorsque l'on en modifie un seul.

N Complexité=

L ~i

(i,j=l)

A B c D E A 11.0 0.3 0.4 0.2 0.1 B 0.3 1. 0 0.2 0.5 0.1 c 0.4 0.2 1. 0 0.2 0.2

D 0.2 0.5 0.2 1. 0 0.1 E 0.1 0.1 0.2 0.1 1. 0

Figure 8.1: Exemple de matrice complète

La matrice de dépendance du premier ordre est obtenue en trois étapes. D'abord, on calcule la matrice de couplage C. Les éléments Cij représentent le couplage entre le module i et le module j . Cette matrice est une matrice symétrique dont tous les éléments se trouvant sur la diagonale sont égaux à 1. Ensuite, on détermine la cohésion S de chaque module du programme. Et pour finir, on construit la matrice de dépendance du premier ordre D de la manière suivante:

si Cij ~o

si Cij = 0 si i=j

Dij

=

0.15 (Si + Sj ) + 0.7 Cij Dij=O

Dii = 1.

Une valeur est associée à chaque type de couplage et de cohésion, l'ensemble de ces valeurs est donné dans la figure 8.2.

Couplage

c

Cohésion

s

contenu .95 coïncidentale .95

commun .7 logique .4

externe .6 classique .6

contrôle .5 procédurale .4

timbre .35 communicationnelle .25

données .2 information ne lie .2

fonctionnelle .2

Figure 8.2 : Poids attribués aux différents types· de couplage et de cohésion

De la même manière que précédemment, on peut dériver une mesure de la complexité en sommant tous les éléments de la matrice D et en divisant par la dimension de cette matrice.

Cette métrique pose quelques problèmes. Par exemple, la matrice de dépendance complète est donnée symétrique, alors qu'elle ne l'est pas réellement puisqu'il existe un certaine hiérarchie entre les modules. De plus, l'attribution d'un facteur de pondération aux différents types de couplage et de cohésion est subjectif et sujet à caution. En fait, l'idée de quantifier tous les types de couplage et de cohésion est une bonne idée, mais difficilement automatisable.

MesureQ

La mesure Q [Cha 79] est basée sur le flot et le rôle des données d'un mnch1le. T .e,s rôles possihles sont: des données utilisées pour produire d'autres données en sortie, des données créées ou modifiées dans un module, des données utilisées pour contrôler l'exécution d'un module, ou encore des données utilisées mais non modifiées. Cette métrique est aussi utilisée pour détenniner la complexité intra-modulaire: elle est expliquée plus en détail dans le chapitre suivant.

Cette métrique pose encore un problème pour son automatisation.

En effet, certaines catégories de données sont difficilement identifiables.

Par exemple, comment peut-on décider que telle donnée d'un module contrôle l'exécution de cet autre module?

Mesure du flot global

Une autre métrique, toujours basée sur le flot des données, est celle détm1e par Henry et Kafura. Elle consiste à compter le nombre de données suivant leur type d'accès. Ainsi, il existe trois différents types: les données (r) accédées en lecture seulement, les dormées (w) créées ou modifiées et les données (rw) lues et modifiées. La mesure finale est obtenue comme suit:

(w x r) + (w x rw) + (rw x r) + (rw(rw-1)).

Cette mesure du flot des données est associée à une mesure de complexité du module. Ces deux mesures sont e.xpliquées plus en détail dans le chapitre précédent.

Cette mesure est facilement réalisable et automatisable. Le seul problème est dans l'objectivité de la formule, car elle ne traduit pas quelque chose d'i.ntuitivement évident. En effet, il est difficile de comprendre quel poids est associé à chacune des différentes classes de données.

Rapport arcs sur noeuds

Cette métrique, appelée arc-to-node ratio (ANR), donne une idée de la complexité de la structure complète du graphe des dépendances. Elle consiste simplement à calculer Je rapport suivant [Ove 82):

nombre d'arcs ANR nombre de noeuds

La valeur théorique minimum est égale à 1 (en fait, (n-1)/n); elle est attein:e pour une structure purement hiérarchique. La valeur maximale est égale à (n2-n)/n = n-1. En général, ce rapport varie entre l et 10, il exprime le nombre moyen d'arcs arrivant ou partant d'un module, c'est-à-dire le fan-in ou le fan-out.

Cette mesure présente l'avantage d'être simple, facilement automatisable, et intuitivement compréhensible et satisfaisante.

Evidemment, on peut lui reprocher de ne prendre que peu d'éléments en considération, mais elle pe1met quand même de donner une bonne indication de la complexité de la structure modulaire d'un logiciel.

On retrouve là tout le problème des métriques: soit elles sont simples, intuitivement satisfaisantes et facilement automatisables, soit elles sont complexes, peu compréhensibles et difficilement automatisables.

Généralement, les méthodes complexes, dont la signification n'est pas évidente, ne sont guère utilisées. Il suffit de regarder les métriques intra-modulaires pour s'en convaincre. En effet, les seules métriques largement utilisées sont les métriques les plus simples à comprendre et à implanter.

Métrique de contrôle et de transformation

Wallace et Nance [Wal 85) ont défini une nouvelle métrique prenant en compte la complexité de la structure globale et la complexité de chaque noeud. Il se sont basés sur les métriques précédentes en ne gardant que les aspects positifs de chacune d'entre elles. Après plusieurs extensions différentes, ils ont abouti à la métrique suivante:

A N MC=NlTCi

i=O

où MC = complexité du modèle,

TCi = complexité de transformation du noeud i, A == nombre d'arcs,

N = nombre de noeuds.

La complexité de transfonnation est définie en combinant la mesure Q avec la mesure du flot global. Ainsi, la complexité du noeud i est définie comme suit

TCi

=

rwx2+ w x 12+ rx 1 1.

Intuiùvement, cette mesure est beaucoup plus satisfaisante que celle définie par Henry et Kafura, car les poids attribués aux différents types de données sont clairs et satisfaisants.

8.2 LA MÉTRIQUE EM2/COMPLEX 8.2.l But de EM2/Complex

La métrique EM2/Complcx est ln métrique développée pour l'environnement EM2. Dans ce chapitre, nous nous intéressons à la partie traitant de la complexité inter-modulaire. Cette métrique se base sur une analyse statique du code pour évaluer la complexité. des dépendances modulaires. Cette complexité est décomposée en deux sous-classes: la complexité des dépendances prises deux à deux et la complexité de l'ensemble de toutes les dépendances.

Etant donné que la complexité inter-modulaire est un domaine relativement neuf pour lequel il n'existe pas beaucoup de métriques, nous avons décidé d'adopter l'approche suivante: réaliser un outil qui implante nos métriques initiales, mais qui soit suffisamment flexible pour pennettre de s'adapter à des modifications de celles-ci. Cette approche nous permettra d'expérimenter diverses métriques. Pour cela, nous utilisons une base de données dans laquelle nous stockons les élémenrs de base des métriques. Nous examinerons la structure de cette base dans le paragraphe portant sur la réalisation.

Peu de travaux portant sur la complexité .inter-modulaire ont été effectués. Nous citerons uniquement l'étude réalisée par Myers [Mye 78]

dont l'intérêt réside dans la définition des concepts de couplage el de cohésion. Ces deux concepts ont été définis pour une notion de module prise au sens large: elles ne sont pas très bien adaptées à la notion de module au sens des langages modulaires. Nous adapterons ces concepts à notre idée et nous franchirons un pas supplémentaire en évaluant la complexité de la structure modulaire globale d'un logiciel, ce qui confè.re une certaine originalité à ce travail.

Pour aboutir à une mesure, les métriques se basent sur divers critères. Concernant la modularité, les principaux critères reconnus sont l'indépendance, la hiérarchie, la réutilisation, l'abstraction, etc ... Nous

considérons que le critère d'abstraction est un des plus importants; son application dans un logiciel s'exprime par la définition et l'utilisation de types abstraits.

8.2.2 Liens entre deux modules

Nous nous concentrons maintenant sur les liens existant entre deux modules: nous allons examiner quelles en sont les caractéristiques importantes.

Les dépendances entre deux modules sont caractérisées par le type des liens qui les relient, le type d'information qu'ils échangent et le volume d'informations échangées.

Couplage et cohésion

Dans le chapitre précédent, nous avons présenté les différents types de couplage et de cohésion. Nous nous sommes rendus compte de la difficulté à réaliser un outil qui automatise leur identification.

Si l'on considère le couplage et la cohésion pour des modules définis à l'aide de langages modulaires, on est obligé de réadapter les différentes classes. Une première adaptation a été effectuée par Macro et Buxton [Mac 87), mais elle n'est pas complètement satisfaisante en ce qui concene Je couplage, car elle repose sur une notion assez large de module pour prendre en compte des langages classiques comme Fortran ou Pascal.

Leur classification de la cohésion définit 5 types : coïncidentale, logique, temporelle, fonctionnelle et abstraite. La cohésion coïncidentale correspond à un module dont les éléments n'ont pas de liens significatifs.

La cohésion logique se réfère à des éléments liés vaguement par une fonction externe (par exemple, un module qui regroupe toutes les fonctions d'entrée/sortie). La cohésion temporelle indique des objets utilisés au même endroit et au même moment, typiquement, un module d'initialisation. La cohésion fonctionnelle traduit des éléments liés par une forte fonction logique. La cohésion abstraite correspond à un module implantant un type abstrait.

Pour nous, le but n'est pas de donner une description exacte d'un logiciel, en indiquant quel est le type exact de la cohésion de chaque module, mais d'évaluer la complexité de ce logiciel. Pour cela, les types de cohésion qui génèrent la même complexité sont mis en commun. Ainsi, nous considérons que seuls les modules ayant une cohésion abstraite ou fonctionnelle sont acceptables. et que les autres types de cohésion génèrent une plus grande complexité. Nous pénaliserons les modules qui n'ont pas une cohésion abstraite ou fonctionnelle, car un logiciel peut être consbuit

uniquement à l'aide de types abstraits [Lis 74]. Les types abstraits algébriques [Lis 75], outil de spécification, viennent renforcer cette affirmation.

L'automatisation d'un outil d'analyse de la cohésion est maintenant plus simple: il suffit de distinguer deux classes de modules. Pratiquement, là différentiation des modules ayant une cohésion fonctionnelle ou abstraite des autres modules s'effectue principalement par une analyse du type des paramètres des objets procédure des modules. Pour qu'un module soit déclaré type abstrait, il faut que les objets procédure aient, au moins, un paramètre dont le type correspond au type constituant le type de base du type abstrait. De la même manière, pour vérifier qu'un module est de type fonctionnel, on vérifie que ses objets procédure travaillent sur les mêmes types de données. Dans les cas litigieux, on regarde si les différents objets d'un module ont des dépendances communes avec d'autres objets. Il faut remarquer qu'un module ayant une cohésion fonctionnelle peut être considéré comme un type abstrait quelque peu dégénéré.

Donc, en ce qui concerne la cohésion, on classifie les modules en deux catégories: type abstrait (ou fonctionnel) et les autres. On pénalise le deuxième type en lui accordant un grand facteur de complexité.

Pour le couplage, Macro et Buxton [Mac 87) ont défmi aussi 5 types différents dont le type import-export qui indique justement le type de couplage entre des modules, où la notion de module est celle des langages modulaires. Donc, il n'y a pas besoin d'effectuer une analyse du couplage pour identifier les relations entre Jes modules pris deux à deux, car ils appartiennent tous à la même catégorie import-export.

Elanl donné que, selon cette définition, les critères de couplage ne nous sont d'aucune utilité, nous allons r~pn:uù.te la notion de couplage en nous basant sur d'autres critères. Nous allons différencier les liens de type constructeur de ceux d'utilisateur. Ces deux types vont jouer un rôle important dans le sens où le lien constructeur va nous servir à fonner des groupes de modules. Nous approfondirons cette notion de groupe de modules dans le paragraphe suivant ponant sur la structure du graphe des dépendances modulaires.

Liens de type constructeur et utilisateur

Un objet d'un module peut utiliser des objets d'autres modules. En général, ces liens sont du type utilisateur. Mais dans certains cas, ces liens traduisent une dépendance plus forte et de nature différente que celle d'une simple utilisation: ce sont des liens du type constructeur. Deux éléments sont liés par un lien de type constructeur si l'un des deux est construit sur l'autre, c'est-à-dire s'il utilise l'autre comme principal

support pour sa propre réalisation. Par exemple, un type abstrait peut être construit sur un ou plusieurs autres types abstraits. D'ailleurs, ce type de liens ne peut se produire qu'entre des modules ayant une bonne cohésion (abstraite ou fonctionnelle).

La différentiation automatique de ces types de liens n'est pas très aisée, nous allons nous baser sur des méthodes statistiques pour la réaliser.

En effet, nous avons établi, d'après examen de plusieurs cas, que lorsqu'un module est construit sur un ou plusieurs autres, il utilise une majorité des objets de ces modules et une minorité d'autres objets. Evidemment, ceci n'est vrai que pour la plupart des cas, il reste toujours des cas délicats.

Au niveau des spécifications, une règle, généralement admise, indique qu'il existe un lien de type constructeur entre deux types abstraits si l'un des types est défini à l'aide de l'autre type. On ne s'occupe pas de l'utilisation des opérations associées au type utilisé. Cette approche ne nous semble pas satisfaisante au niveau des langages de programmation car insuffisante et pas assez restrictive.

Pour établir les différents types de liens (ou de couplage), nous proposons les mesures suivantes, basées sur le nombre d'objets appartenant au module X et le nombre d'objets utilisés dans le module A, où le module A appartient à un niveau hiérarchique supérieur à celui du module X.

nombre d'objets du module X utilisés dans le module A nombre total d'objets du module X

nombre d'objets du module X utilisés dans le module A nombre total d'objets utilisés dans le module A

La première mesure donne le rapport d'objets du module X utilisés dans le module A, alors que la deuxième donne le rapport d'objets du module X utilisés dans le module A. Le nombre total d'objets utilisés dans le module A est calculé en enlevant tous les objets provenant des modules de librairie. Il faut maintenant indiquer quelles sont les valeurs limites, au-dessus desquelles on attribue le type constructeur. Pour la première mesure, on considère que la valeur se situe aux alentours de 2/3. Cette valeur peut sembler facile à atteindre pour des modules comportant peu d'objets, mais elle devient difficile pour des modules ayant un grand nombre d'objets. La valeur limite de la deuxième mesure se situe aussi aux alentours de 2/3, s'il n'y a qu'un module de base mis en cause. Mais supposons que le module A soit un type abstrait construit sur deux modules type abstrait X et Y. Alors, on ne peut pas exiger une valeur de 2/3 pour chacun de ces modules.

Les valeurs limites sont des valeurs subjectives déterminées de manière empirique. Une autre possibilité consisterait à effectuer une classification plus fme selon ct:s valeurs, c'est-à-dire à définir des classes déterminées par de petits intervalles consécutifs. Dans notre première approche, nous préférons nous contenter de deux classes séparées par une valeur limite.

Volume de données

Le volume des données échangées permet d'avoir une indication sur la complexité des échanges entre deux modules. Ce volume est calculé de manière statique: il est fonction des composants des types des objets ou de Jeurs paramètres, comme, par exemple, les paramètres d'une procédure importée.

Le volume (VD) est déterminé par les règles suivantes:

- soit un module A subordoIUlé à un module B, alors m

n m Clïj

Di

VDab =

~ L...J ±

I=l Cl ij X Di

= =

=1

=0

nombre d'objets du module j=l A, nombre d'objets du module B,

s'il existe un lien entre le ième objet de A et le jème objet deB,

sinon,

nombre de composants él~me:ntaires des données échangées.

Remarque : le module A est subordonné au module B signifie que le module B importe ou utilise des objets du module A. Les composants élémentaires sont tels que définis par le langage MODULA-2.

Résumé

En résumé, pour chaque module, on indique sa cohésion (abstraite ou non), son couplage (constructeur ou utilisateur) avec les autres modules et le volume des données échangées lors de ces couplages.

8.2.3 Structure du graphe des dépendances modulaires

La modularité, au sens des langages ADA ou Modula-2, pennet de décomposer un logiciel en un certain nombre de modules. Il existe des méthodes facilitant la décomposition d'un projet en modules [Par 72) [Wie 84), mais la part laissée à l'intuition du concepteur est encore assez importante pour que la décomposition modulaire d'un projet soit prôpre à ce concepteur. En règle générale, si plusieurs concepteurs développent un même projet, il est quasiment certain que leurs décompositions modulaires seront différentes. Il faut aussi remarquer que la qualité de la structure d'un logiciel, aussi bonne soit elle initialement, a tendance à se dégrader au cours de la vie de ce logiciel.

Topologie du graphe des dépendances modulaires

Le problème de la structure modulaire d'un programme a été analysé depuis longtemps d'une façon très générale. Ains~ Wirth [Wir 74]

a décrit cette structure en termes de niveaux d'abstraction, où les niveaux inférieurs correspondent à un raffinement croissant des niveaux supérieurs. D'une manière similaire, Dahl, Dijkstra et Hoare [Dah 72] ont défini chaque niveau comme une ressource abstraite ou une machine virtuelle, fournissant des outils de base au niveau supérieur, et réalisé à l'aide du niveau inférieur.

Depuis, on est arrivé à l'hypothèse suivante: un programme bien structuré donne lieu à un graphe de dépendances hiérarchique et arborescent [Tur 80). Le critère de hiérarchie implique que les modules soient répartis sur des niveaux distincts, et celui d'arborescence implique qu'il n'y ait pas de partage d'un module entre plusieurs modules de la couche supérieure. Nous avons déjà aperçu un certain nombre de critères de modularisation au paragraphe 5.2.5 du chapitre précédent. On s'est rendu compte que ces deux critères ne sont pas entièrement compatibles avec ceux de types abstraits ou de réutilisation.

Nous pensons qu'il faut trouver un compromis entre tous ces critères en privilégiant la cohésion, et ensuite, en essayant de minimiser le nombre de connexions. Selon Yourdon [You 75], le nombre d'erreurs est directement proportionnel au nombre de connexions. Nous pensons que la cohésion est le facteur le plus important car un module ayant une bonne cohésion (type abstrait ou fonctionnelle) constitue une brique de base cohérente et fiable. Si au contraire, on commence par privilégier le nombre de connexions, on peut déboucher sur une structure relativement simple, mais où la cohésion des modules atteint un niveau déplorable.

L'exemple suivant illustre ce point de vue.

Notre exemple montre la structure modulaire obtenue lorsque l'on

Notre exemple montre la structure modulaire obtenue lorsque l'on