Pourquoi
des packages ?
A haut niveau (spécification) , il
s'agit de résoudre un sous-problème, ce qui est
plus simple que de s'attaquer à tout.
La difficulté de cet exercice réside dans la
détermination des services
que doit rendre un package.
A bas niveau (conception - codage),
il s'agit pour vous, jeune programmeur de la vie
de tous les jours, de livrer pour l'avant-veille
une librairie de classes qui, compilée avec
d'autres, permet d'obtenir un exécutable.
La difficulté de cet exercice, c'est que le bon
fonctionnement de votre librairie repose sur
d'autres librairies développées par d'autres
équipes, et que vous devez rester compatibles
avec elles au jour le jour...
Cet article aborde deux grands problèmes connus
avec la manipulation de ces ensembles de classes
:
- l'organisation
des packages
- le
degrés de dépendance des packages
Des packages de packages ?
Depuis le début de
cet article, il est fait allusion à deux
"niveaux" (haut niveau et bas niveau).
Si ces termes vous gênent, vous pouvez
également les interpréter comme les niveaux
"logique" et "physique" ou
"abstraits" et "concrets" ou
"théoriques" et "pratiques"
ou ...
Bref, dans le
premier (où l'on analyse, une fois
l'expression des besoins clairement identifiée),
il est concevable d'imaginer des packages de
packages. Il s'agit d'une démarche naturelle
"en profondeur d'abord" où l'on part du problème général
pour aller vers des problèmes plus spécifiques.
Les ennuis arrivent au deuxième
niveau, celui auquel les programmeurs se trouvent
confrontés alors qu'ils n'ont parfois qu'une
vague notion des spécifications (quand ils ont
la chance d'en avoir tout court!).
Ils ont alors tendance d'adopter une démarche
similaire qui peut se révéler... très gênante
:
Imaginez que vous soyez chargé de développer
deux packages A1 et A2 que vous les regroupiez
sous le package unique A

A votre niveau (conception-codage), cela va vite
poser un problème d'organisation pratique :
Que se passe-t-il lorsqu'un de vos
collègues, ébloui par le package "A2"
(qui répond pile-poil à ses besoins), veut vous
le prendre ?
Sera-t-il obliger de créer un sur-package
"A" dont il n'a que faire ?
De plus, certains langages ont
besoin d'un chemin précis pour inclure les
classes d'un autre package dont A2 pourrait
dépendre. Imaginez que A2 dépendent de A1. Le
code que vous lui livrez comporte donc des
chemins d'inclusion du type
"A/A1/class1".
Et alors ? Il lui suffit, à votre collègue, de
se créer un répertoire "A" dans
lequel il mettra vos bô packages! Oui, mais : il
a déjà un répertoire "A" qui a, pour
lui, une signification toute autre que celle que
vous lui avez donné dans votre conception. Et il
a déjà mis 125 autres packages qui n'ont pas
grands liens logiques avec les deux packages que
vous devriez lui livrer...
Tout cela pour dire quoi ?
Pour dire qu'au
niveau du code, vos ensembles de classes
devraient être organisés en packages feuilles.
Vous devriez avoir une collection
de packages sous une racine commune. Les
avantages sont les suivants :
* vous ne vous perdez pas en parcours
d'arborescences compliquées : en un niveau, vous
avez la classe cherchée.
* La classe du package A2 qui dépend du package
"A1" (ou de n'importe quel autre
package) a un chemin de dépendance simple qui ne
comporte que deux éléments :
1/ le nom de l'autre package,
2/ le nom de l'autre classe
* La réutilisation de ce package dans d'autres
projet est simple : il suffit de le recopier sous
une racine unique, en compagnie de tous les
autres packages propres au projet de votre
collègue.
Ce principe n'est bien sûr pas à appliquer tout
le temps : regrouper des classes dans des
ensembles logiques qui eux même sont regroupés
dans des sur-ensembles logiques est une démarche
naturelle qui existe...
Mais considérez un instant où elle existe :
l'exemple le plus fameux est celui des JDK (Java
Development Kit) de Java. Toutes leurs classes
sont organisées de façons arborescentes et non
en râteau (packages feuilles) comme cela était
préconisé dans le paragraphe précédent...
Oui mais : il s'agit la d'un produit fini,
longuement mûri et "stabilisé",
distribué dans le monde entier. Il s'agit alors
de mettre à disposition la collection de
packages précédemment évoquée d'une façon
suffisamment pratique pour que les clients
puissent s'y retrouver.
Donc ? Donc, tant que vos packages sont
développés dans un environnement de travail (et
non dans une version "release"),
destinés à être réutilisés par par d'autres
équipes internes à votre entreprise, la vision
"collection de packages feuilles", en
râteau sous une racine unique, reste
préférable : chacun va
"y faire ses courses" et importer le ou
les packages dont il a besoin. |
Un
package qui dépend d'un autre package
Faire dépendre un
package d'un autre est une décision lourde de
conséquences, qui renvoie au problème de couplage
déjà évoqué en phases d'Analyse
et de codage.
La première question à bien se
poser lorsque l'on choisit de faire dépendre un
package "A" d'un autre "B" ,
c'est :
« pourrai-je facilement remplacer "B"
par autre chose ? ». La réponse est oui si les services
qu'offre le package "B" sont clairement identifiés, pas trop nombreux et adaptés.
Si c'est la cas, cela veut dire que n'importe
quel autre package offrant les mêmes services
que "B" fera l'affaire.
Donc ? Donc on ne devrait
dépendre d'un package que si les services
proposés par ce dernier contribuent tous
(ou au moins en très grande partie) à répondre
aux demandes du premier package.
La deuxième question se déduit de
la première et repose sur le principe de stabilité.
Si l'on considère la chaîne de dépendance
suivante :

|
Il est très important de se demander si
"D" est susceptible de
"changer" souvent (nouveaux services,
changement d'interface, de signatures de
fonctions, besoin de dépendre à son tour
d'autres packages dont l'utilisation pourraient
se révéler incompatibles avec "A",
"B" et "C", actuels
utilisateurs de "D").
Normalement, "D" devrait être le
package offrant des services clairement
identifiés, stabilisés et évoluant peu. Dans
l'idéal, "D" ne doit dépendre de rien
d'autre.
Pour ne pas changer, la troisième
question se déduit de la deuxième et repose sur
le principe d'abstraction.
En fait, plus un package
est dit "stable", plus il devrait être
composé en grand nombre de classes abstraites.
Les packages clients auraient la charge de
fournir une implémentation concrète,
implémentation susceptible de changer,
entraînant une plus grande instabilité
potentielle des services du packages.
Java a popularisé cette notion avec ses packages
constitués uniquement d'interface dont chaque
éditeur propose ensuite son implémentation.
Après vous être posé ces trois
questions, vous avez décidé de faire dépendre
le package "C" du diagramme ci-dessus
de "A'".
Il vous reste alors une dernière question à
vous poser : « ne suis-je
pas en train d'introduire un cycle de
dépendances ? ». ce cycle se repère en
"suivant les flèches", exercice facile
dans le cas de notre exemple, mais très vite
complexe lorsque le nombre de packages est
élevé.

|
Si oui, cela est une très mauvaise idée car
toute modification (pour quelque raison que ce
soit) d'une des classes d'un des trois packages
affectera potentiellement les deux autres.
Du coup, les enjeux exposés dans le paragraphe
suivant seront beaucoup plus difficiles à
atteindre.
Un politique courante consiste à les proscrire
purement et simplement.
Enjeux
Pourquoi se poser en
permanence ces questions ? Trois grands enjeux en
dépendent :
Evolutivité :
un package aux services bien pensés doit pouvoir
être soit enrichi facilement, soit pouvoir
changer son mécanisme interne sans tout remettre
en cause.
(Ré)-utilisation
: vos packages vont être utilisés par d'autres
programmeurs au sein même du projet auquel vous
participez. Ou bien, ils vont être réutilisés
dans d'autres projets du même service
informatique. Il est indispensable de leur
fournir des services clairs, avec derrière une
implémentation privée (interne au package)
suffisamment souple pour être facilement
modifiable et s'adapter à des exigences pas
forcément prises en compte dès le départ (un
exemple typique est le problème des performances!
Ce que vous livrez répond bien au besoin mais se
traîne lamentablement!).
Maintenance : lorsqu'un bug
arrive, c'est le moment de vérité.
Normalement, il ne doit affecter qu'un seul
package. Sa correction ne devrait entraîner en
principe aucune autre dépendance.
Dès que la correction d'un bug implique plus
d'un package, les problèmes commencent, car,
bien sûr, l'autre package concerné n'est pas
toujours de votre ressort.
Pour conclure, respecter
les principes précédemment évoqués, c'est
s'épargner bien des angoisses et du temps dans
les phases ultérieur du développement ou de la
vie de votre logiciel...
|