Les principes du génie logiciel
Ce que ce chapitre apporte
- Définir le génie logiciel et dire ce qui le distingue de la programmation.
- Nommer les critères de qualité d'un logiciel et savoir les opposer entre eux.
- Situer les activités du cycle de vie et les grands modèles de processus.
- Expliquer ce qu'est la dette technique et comment elle se contracte.
- Employer les notions de couplage, de cohésion et de séparation des responsabilités.
- Distinguer vérification et validation.
Ce que recouvre le génie logiciel
Le génie logiciel est l'ensemble des méthodes, techniques et outils permettant de produire et de faire évoluer un logiciel dans des conditions maîtrisées de coût, de délai et de qualité.
La programmation s'arrête quand le programme donne le bon résultat. Le génie logiciel commence là. Il s'intéresse à ce que coûtera la modification suivante, à ce qui se passe quand trois personnes travaillent sur le même code, à ce qui reste compréhensible une fois l'auteur parti.
Cette différence n'est pas une question de taille de programme. Un script de cinquante lignes exécuté chaque nuit depuis quatre ans relève du génie logiciel, parce qu'il faut pouvoir le corriger. Un prototype de dix mille lignes jeté après la démonstration n'en relève pas.
Les critères de qualité
La norme ISO 25010 en distingue huit. Six suffisent à couvrir l'essentiel de ce qui se discute en conception.
| Critère | Ce qu'il mesure |
|---|---|
| Fonctionnalité | le logiciel fait-il ce qui est demandé |
| Fiabilité | se comporte-t-il correctement dans la durée, y compris en cas d'erreur |
| Performance | consomme-t-il des ressources raisonnables |
| Utilisabilité | est-il compréhensible par celui qui s'en sert |
| Maintenabilité | combien coûte une modification |
| Portabilité | fonctionne-t-il ailleurs que sur la machine de développement |
Aucun logiciel n'est excellent sur les six axes. Une décision de conception privilégie certains critères aux dépens d'autres, et la seule vraie faute consiste à ne pas savoir lesquels on a sacrifiés.
Le premier choix privilégie la maintenabilité, le second la performance. Les deux se défendent. Ce qui ne se défend pas, c'est de choisir sans savoir ce qu'on échange.
Le cycle de vie
Les mêmes activités se retrouvent quel que soit le processus employé pour les enchaîner.
- Recueil et analyse des besoins : ce que le client demande, et ce dont il a besoin, qui ne coïncident pas toujours.
- Spécification : ce que le logiciel fera, formulé de façon vérifiable.
- Conception : comment il sera structuré. Les chapitres sur UML, les patrons et l'architecture traitent de cette phase.
- Implémentation : l'écriture du code.
- Tests : unitaires, d'intégration, de recette.
- Déploiement : la mise à disposition.
- Maintenance : corrective, évolutive, adaptative.
Cela renverse une intuition répandue. Le temps consacré à rendre un code lisible n'est pas retiré au développement : il est investi dans la phase qui coûtera le plus cher.
Les modèles de processus
| Modèle | Principe | Contexte d'emploi |
|---|---|---|
| Cascade | les phases s'enchaînent, chacune achevée avant la suivante | besoins figés, contraintes réglementaires |
| Cycle en V | cascade, avec un niveau de test associé à chaque phase de conception | systèmes critiques, recette contractuelle |
| Incrémental | livraison par morceaux fonctionnels successifs | le besoin se précise en avançant |
| Itératif, agile | livraisons fréquentes, ajustement à chaque itération | besoin instable, retour client régulier |
Le choix se joue sur la stabilité du besoin. Un système embarqué certifié se développe en V parce que la recette est contractuelle et que chaque exigence doit être tracée jusqu'à un test. Une application métier dont les utilisateurs découvrent leurs attentes en s'en servant se développe par itérations.
La bonne discipline est plus modeste. Il s'agit d'éviter d'écrire du code qui interdirait la suite : des variables globales partagées qui empêcheront le parallélisme, une écriture directe dans un fichier qui empêchera de changer de support, une classe qui suppose qu'un seul traitement tourne à la fois.
La dette technique
La dette technique désigne le coût futur des raccourcis pris aujourd'hui. Comme une dette financière, elle porte des intérêts : plus le remboursement tarde, plus chaque modification devient coûteuse.
Elle se contracte de plusieurs manières, et toutes ne sont pas fautives. Une équipe peut décider de livrer une solution imparfaite pour tenir une date, en sachant ce qu'elle devra reprendre. La dette est alors un instrument de gestion. Ce qui pose problème, c'est la dette contractée sans qu'on s'en aperçoive : du code recopié parce que c'était plus rapide, des noms qui ne veulent rien dire, des fonctions qui font six choses.
Quelques symptômes permettent de la reconnaître avant qu'elle ne bloque :
- Une petite modification oblige à toucher beaucoup d'endroits.
- Personne ne sait dire si une fonction est encore appelée.
- Corriger un défaut en fait apparaître un autre ailleurs.
- Les nouveaux arrivants mettent des semaines à devenir productifs.
Trois notions de conception
Elles reviennent dans tous les chapitres suivants, et la plupart des décisions d'architecture se justifient par l'une d'elles.
Chaque module, chaque classe, chaque fonction assume une responsabilité. Une classe qui lit une configuration, calcule un résultat et écrit un journal en assume trois, et devra donc changer pour trois raisons indépendantes.
Le couplage mesure la dépendance entre modules. On le veut faible : modifier l'un ne doit pas obliger à modifier les autres.
La cohésion mesure à quel point les éléments d'un même module concourent au même but. On la veut forte.
Ces deux mesures varient souvent en sens inverse d'une mauvaise conception : un module qui fait trop de choses a une cohésion faible, et il finit couplé à beaucoup d'autres parce qu'il a besoin de tout.
Viennent ensuite des règles plus terre à terre : ne pas dupliquer, nommer explicitement, écrire court, laisser le code se lire plutôt que de le commenter partout. Et tester.
Un projet sans tests se fige : chacun évite le code existant et le contourne, ce qui augmente encore la dette.
Vérification et validation
L'assurance qualité logicielle regroupe les activités destinées à donner confiance dans le fait que le logiciel satisfera les exigences : revues, tests, mesures, respect des processus.
Elle recouvre deux contrôles distincts que l'on confond souvent.
La vérification demande si le produit a été construit correctement, c'est-à-dire conformément aux spécifications. La validation demande si c'est le bon produit qui a été construit, celui qui répond au besoin réel.
Un logiciel peut passer toutes ses vérifications et échouer à la validation : il fait exactement ce qui était écrit, et ce qui était écrit ne correspondait pas au besoin. Cette situation coûte cher parce qu'elle se découvre tard, souvent à la recette.
Exercices type
Un client demande que le traitement soit « rapide ». Que faire de cette exigence ?
La refuser en l'état et la reformuler.
« Rapide » ne se vérifie pas. On ne peut ni concevoir contre cette exigence, ni dire à la livraison si elle est tenue.
Une exigence devient vérifiable quand elle est chiffrée et située : traiter 10 Go de fichiers de 2 Mo en moyenne en moins de cinq minutes sur une liaison gigabit. On peut alors mesurer, et discuter.
Ce travail appartient à la spécification. Une exigence floue produit un désaccord au moment de la recette, quand il n'est plus temps.
Pourquoi la maintenance coûte-t-elle plus cher que le développement ?
D'abord parce que la vie d'un logiciel dépasse largement sa construction : quelques semaines d'écriture, plusieurs années d'exploitation pendant lesquelles il faut corriger, adapter et étendre.
Ensuite par un effet de composition. Chaque modification faite sans soin rend la suivante plus difficile, si bien que le coût unitaire d'une modification augmente avec l'âge du logiciel, sauf si l'on entretient sa structure.
La conséquence pratique porte sur la première version : le temps consacré à découper proprement conditionne le coût de toutes les suivantes.
Distinguer vérification et validation sur un exemple.
La spécification indique : « le journal enregistre l'horodatage, l'identifiant du traitement, la taille traitée et la durée ».
Vérification : le journal contient-il ces quatre champs, au bon format ? Un test automatisé répond.
Validation : ces quatre champs permettent-ils réellement à l'exploitant de diagnostiquer un échec ? Il manque peut-être le message d'erreur, ou le chemin du fichier fautif.
Le logiciel peut réussir la première et échouer la seconde. C'est la différence entre construire le produit correctement et construire le bon produit.
Est-il toujours mauvais de contracter de la dette technique ?
Non. Livrer une solution imparfaite pour tenir une échéance, en sachant ce qu'il faudra reprendre, est une décision de gestion défendable.
Deux conditions la rendent acceptable. Elle doit être consciente : on sait ce qu'on a simplifié. Elle doit être consignée, avec une estimation du coût de remboursement.
La dette problématique est celle qu'on contracte par ignorance, qui ne figure nulle part, et qu'on découvre le jour où elle empêche une évolution.
Quel critère de qualité sacrifie-t-on en dupliquant du code ?
La maintenabilité, tout de suite et durablement.
Recopier une fonction fait gagner quelques minutes. Le jour où le comportement change, il faut retrouver toutes les copies. Celles qu'on oublie créent des incohérences difficiles à diagnostiquer, puisque le logiciel se comporte différemment selon le chemin emprunté.
La fiabilité en souffre aussi : un correctif appliqué à une seule copie laisse le défaut vivant ailleurs.
La duplication n'améliore aucun autre critère, ce qui la distingue des vrais arbitrages. Ce n'est pas un compromis mais une perte différée.
Pourquoi exiger des tests avant d'autoriser une refonte ?
Une refonte modifie la structure sans changer le comportement. Sans moyen de vérifier que le comportement est resté identique, l'opération relève du pari.
Les tests jouent le rôle de filet : on déplace, on découpe, on renomme, et l'on sait en quelques secondes si quelque chose a cassé.
Cela explique la spirale des projets sans tests. Le code se dégrade, personne n'ose le reprendre faute de garantie, la dette s'accumule, et le coût de la moindre évolution finit par dépasser celui d'une réécriture.
La méthode
- Rends chaque exigence vérifiable avant de concevoir. Ce qui ne se mesure pas ne se livre pas.
- Nomme les critères de qualité prioritaires, et ceux que tu acceptes de sacrifier.
- Découpe par responsabilité : une classe, une raison de changer.
- Vise un couplage faible entre modules et une cohésion forte à l'intérieur de chacun.
- N'écris pas de code qui interdise la suite, sans pour autant implémenter d'avance ce qui n'est pas demandé.
- Consigne toute dette contractée sciemment, avec son coût estimé.
- Écris les tests avant la refonte, jamais après.
En résumé
- Le génie logiciel s'occupe du coût de la prochaine modification, pas seulement de la justesse du code.
- Les critères de qualité entrent en concurrence. Concevoir consiste à choisir, et à l'assumer.
- Le cycle de vie va du besoin à la maintenance, phase la plus coûteuse.
- Cascade et cycle en V pour un besoin figé, incrémental et itératif quand il évolue.
- La dette technique est acceptable si elle est consciente et consignée.
- Séparation des responsabilités, couplage faible, cohésion forte gouvernent la plupart des décisions de conception.
- Les tests servent surtout à autoriser le changement.
- Vérification : le produit est-il conforme. Validation : est-ce le bon produit.
Et ensuite ? Plusieurs personnes vont modifier le même code en parallèle. Il faut savoir qui a changé quoi, revenir en arrière, et fusionner des travaux concurrents. C'est l'objet du chapitre suivant, consacré à la gestion de versions.