dimanche 14 février 2016

Mythes et légendes de la POO 1/2

Dans cet article, je vais essayer de m'attacher à vulgariser quelques concepts liés à la programmation objet, et en profiter pour en démystifier certains autour desquels j'ai entendu trop de bullshit depuis des années. Les concepts que je vais aborder sont très généraux et nous allons les étudier sans plonger dans le code, avec une vision haut niveau afin de bien en faire ressortir les éléments clés.

Un premier niveau de compréhension des concepts objets, en particulier l'héritage, la composition et le polymorphisme est parfois requis, mais la plupart de l'article reste accessible sans connaissance préalable.

Design Pattern / (Patron de conception)

Assez logiquement, tous les développeurs travaillant dans un même domaine d'application se retrouvent confrontés aux mêmes problèmes à résoudre. Et chacun va expérimenter différentes façons de faire, sans doute se planter sur ses premiers programmes, puis trouver un tour de main à peu près satisfaisant, puis le repenser et l'améliorer au fil du temps, jusqu'à trouver "la solution parfaite" ; dès lors, il pourra l'appliquer à chaque occasion qui se présentera à nouveau sans devoir refaire le même cheminement ponctué de tâtonnements, de révision déchirantes, et d'engueulades du chef de projet désespéré de voir le budget exploser et les plannings se décaler.

Ne serait ce pas magnifique si l'expérience ainsi acquise pouvait se transmettre ? Si on pouvait écrire un livre de recette décrivant les bonnes façon de faire ? Ainsi, il suffirait de s'y plonger pour avoir directement la quintessence des bonnes pratiques.

Vous en avez rêvé, et bien le GOF l'a fait. Le GOF (pour Gang of Four) désigne 4 légendes vivantes de la POO, 4 ingénieurs d'exception, qui il y a des décennies déjà on écrits ces tours de main, les fameux design pattern, dans des recueils qui doivent figurer sur l'étagère de tout ingénieur.

L'apprentissage de ces pattern est donc une bonne idée pour tout ingénieur qui se spécialise dans le domaine de la POO, et leur existence un atout pour la discipline. Jusque là tout va bien, que du bon sens.

Mais pour autant, il est totalement faux de croire que la connaissance livresque des pattern suffit à faire un bon ingénieur : l'expérience en matière de POO est primordiale (sans parler d'un solide sens pratique).

J'insiste sur ce point car la connaissance des design pattern a été survalorisée pour des raisons purement marketing (c'est un bon argument pour augmenter le tarif journalier d'un expert , ça permet de développer des activités de consulting et de formation...). Et avec un effet de bord sur des développeurs se comportant en "idiots savants" abusant de l'usage systématique des pattern, en dépit du bon sens, en aboutissant généralement à un assez piètre résultat (le risque de sur-conception, de concevoir quelque chose d'inutilement complexe, est un écueil classique et bien connu de la POO).

Donc oui, la connaissance des design pattern est un plus significatif et un pré-requis pour certains postes nécessitant un fort niveau de technicité, mais pour autant ce n'est en rien une garantie de succès ou une recette magique.

MVC

MVC est un design pattern classique. C'est un pattern assez abstrait, disons un concept général, une idée, qui peut se réaliser de multiples façons.

Expliquons rapidement ce qu'est MVC : de façon courante, les logiciels sont architecturés selon un système de couches superposées. Il existe plein de façon de faire mais on trouve en général au moins une couche chargée de la gestion des interactions avec l'utilisateur (l'IHM), et une couche qui implémente les fonctionnalités voulues. La couche IHM fait appel à la couche "fonctionnalités" en fonction des actions de l'utilisateur (par exemple lancer un traitement suite au clic sur un élément de menu), et à l'inverse la couche "fonctionnalités" peut provoquer des mises à jour de l'IHM (par exemple, affichage d'un message pour communiquer le résultat d'une action déclenchée par l'utilisateur). Ces deux couches sont donc fortement couplées et mutuellement dépendantes, ce qui est un gros souci pour la maintenabilité et la fiabilité (sans parler que l'idée de base dans un système en couche superposées, c'est que chaque couche ne doit parler qu'à sa couche inférieure).

Le pattern MVC vise à découpler la couche "IHM" (le V de MVC, pour View) et la couche "fonctionnalité" (le M de MVC, pour Model) afin qu'elles ne se parlent plus directement mais par le biais d'un intermédiaire appelé contrôleur (le C de MVC). ça n'a l'air de rien comme ça, mais ça amène énormément de bénéfices, faîtes moi confiance. C'est aussi simple que ça.

Le monde du développement est régulièrement agité de soubresauts liés à la guerre entre tenants des différents variations autour de ce thème, certains s'en s'ont fait une spécialité et peuvent en parler pendant des heures...moi je trouve ça super chiant, mais bon c'est personnel.

Si je parle de MVC en particulier, c'est car ici  aussi le marketing a fait des ravages... A une époque, on en a fait des caisses sur la mise en oeuvre de ce pattern dans les architectures web, un peu comme si c'était la recette du succès d'un projet... Bien sur il n'en est rien, c'est une bonne pratique et un facteur de succès mais c'est tout. On peut très bien réussir sans et échouer avec.

IOC, injection de dépendance

IOC est encore un pattern abstrait. Mais encore beaucoup plus abstrait et plus généraliste que MVC, en fait je dirais même que le terme de design pattern n'est pas adapté.

Comme MVC, c'est un grand classique des sujets d'ingénierie dont le marketing s'est emparé et dont tout le monde s'est mis à parler, généralement à tort et à travers.

Comme pour MVC, c'est du au fait qu'une technologie innovante et/ou résolvant un problème fréquent a occupée le devant de la scène, et que cette technologie s'appuyait sur ce concept. Pour MVC ça a été le framework web STRUTS (une grosse daube au passage, une preuve de plus qu'en informatique ce ne sont que rarement les meilleures solutions qui deviennent des standards), pour IOC ça a été le framework SPRING qui dans sa première version a popularisé les mécanismes d'injection de dépendance (l'injection de dépendance étant une forme particulière d'IOC).

IOC est un acronyme signifiant Inversion Of Control, inversion de contrôle en Français. Mais de quoi parlons nous ici ?

Le plus simple pour expliquer IOC est de revenir 25 ans en arrière. A l'époque du DOS, avant l'arrivée de Windows et des interfaces graphiques. Imaginons un programme simple : un écran de saisie avec 3 zones de textes. A l'époque, un programme dessinait les zones à l'écran, positionnait le curseur au début de la première zone, et se mettait en attente d'un signal du clavier. Si c'était un caractère autorisé, il l'affichait à l'écran et déplaçait le curseur, sinon il faisait un beep. Si la touche enfoncée était "flèche bas", il déplaçait le curseur sur la zone suivante etc. Bref, le programmeur était le maître du monde, il avait le contrôle total de ce qui se passait. Inutile de dire que le moindre écran prenait un temps fou, raison pour laquelle l'ergonomie était pourrie (car sinon il aurait fallu passer un temps infini sur chaque écran). Puis, on a vu arriver Windows et de nouveaux modèles de programmation (je simplifie à outrance pour l'exemple) : désormais, le programmeur ne code plus la gestion de l'écran, il se contente de le dessiner avec un outil graphique puis il demande à Windows de l'afficher. C'est Windows qui gère tout ce que gérait le programmeur autrefois ; saisie de caractères au clavier, déplacement de zone en zone, plus dimensionnement des fenêtres etc... Tout ce qui est générique et commun à tout écran de saisie est géré par le système, le développeur ne s'en soucie plus, on a un gros gain d'ergonomie et de productivité. Cependant, à un moment donné le programme va faire des choses qui lui sont spécifiques, comme par exemple calculer un montant TTC après qu'on ait saisi un montant HT et un taux de TVA. Et ça le système d'exploitation ne peut pas le prendre en charge car ce n'est pas générique mais spécifique au programme. Il va donc rendre la main au programme au moment voulu (après validation d'un montant HT et d'un taux de TVA par exemple). C'est le programmeur qui aura spécifié au système a quel moment on doit lui donner la main et quel morceau de code il faut appeler à tel ou tel moment. Peu importe la façon de faire, il existe différents mécanismes mais ce qu'il faut retenir c'est que le contrôle est inversé : ce n'est plus le programme qui a le contrôle mais le système chargé d'exécuter le programme, alors qu'auparavant c'était le contraire. Le contrôle est inversé. Voila, c'est aussi simple que ça.

Voyons maintenant l'application de ce principe dans l'univers JEE. C'est un peu plus technique.

JEE est une version de java qui est utilisée pour le développement web. C'est aussi une norme qui est implémentée par des serveurs d'applications. Ces serveurs constituent l'environnement d'exécution des applications web qui sont développées selon certaines conventions spécifiques. Une application web c'est par exemple le site internet de consultation de votre compte bancaire. Quand vous cliquez quelque part, une requête http est émise, interceptée par un serveur http qui la transmet au serveur d'application, qui va exécuter la partie de l'application qui va bien (qui va générer une page html et vous la renvoyer, par exemple). Si ce fonctionnement n'est pas clair pour vous, commencez par lire cette série de 3 articles sur le fonctionnement du web.

Dans cet univers, les serveurs d'application fournissent des services techniques aux développeurs afin de les décharger de certain aspects rébarbatifs et très complexes, afin qu'il puissent se focaliser sur ce qui est vraiment important pour l'application : ses fonctionnalités. Pour cette raison, l'IOC est appliquée à la problématique d'instanciation des classes.

En POO, on doit commencer par instancier une classe, qui est un bout de code spécialisé sur une tâche, avant de pouvoir utiliser ses services (l'instance d'une classe étant un objet). Habituellement, c'est le programmeur qui se charge d'instancier ces classes. Mais en environnement JEE, afin que le serveur d'application puisse rendre les services évoqués, il doit avoir le contrôle de ces instanciations : on a donc une inversion de contrôle ; l'IOC est un concept clé de JEE.

IOC nous l'avons vu est un principe général, et il peut s'implémenter de diverses façons. Dans un projet utilisant les fonctionnalités avancées de JEE (appelées EJB, leur utilisation étant facultative), c'est implémenté via un pattern appelé "service locator". En gros, le programmeur s'adresse au serveur et lui demande une instance de la classe dont il a besoin (au lieu de la créer lui même) ; pour les connaisseurs, c'est bien entendu de l'API JNDI et ses fameux lookup dont il s'agit (sur les EJB, avant la version 3).

JEE a souffert de gros défauts de conception durant très longtemps : c'était une usine à gaz, lente à l'exécution, et complexe à la mise en oeuvre. L'IOC et le pattern "service locator" n'étaient pas en cause mais toujours est il qu'une alternative plus légère est apparue dans le monde de l'open source : SPRING. Cette alternative qui a connue un très grand succès s'appuie également, et pour les mêmes raisons, sur le principe d'IOC appliqué aux instanciations. Mais l'implémentation s'appuie sur un design pattern différent qui est mis en oeuvre d'une façon très astucieuse, pour réaliser ce qu'on appelle l'injection de dépendances (qui est donc une autre forme d'IOC).

Bref, le grand succès de Spring, et le fait que dans leur communication les ingénieurs qui l'ont conçu ont mis en avant le mécanisme d'injection de dépendances, ont générés encore un buzz marketing sur les notions d'IOC et de DI (Dependency Injection). Alors que le vrai débat entre Spring et JEE se situe à un tout autre niveau.

Et au fait, comment ça fonctionne l'injection de dépendances ?

L'idée générale est que Spring a la main (le contrôle) sur toutes les instanciations : le programme écrit par le développeur en s'appuyant sur le framework Spring ne fait aucune instanciation, au lieu de cela il demande a Spring de lui fournir l'objet dont il a besoin (jusque là rien de neuf). Ce qui est novateur, c'est quand l'objet en question a une dépendance sur une autre classe, Spring la détecte grâce à des meta-données fournies par le développeur qu'il a consommé lors de son initialisation (fichiers xml ou annotations java), et dès lors qu'il instancie la classe qui a une dépendance (qui utilise une autre classe), il instancie également, si nécessaire, la dépendance et la passe à la première classe (soit via un appel au constructeur, soit via un appel à une méthode de la première classe qui  va setter la propriété dépendante). Pour en revenir aux design patterns, c'est une implémentation sophistiquée et générique du design pattern "Factory" (un objet chargé d'instancier des objets).

Comme Spring instancie tous les objets, il peut en profiter pour interférer avec leur comportement et apporter des services transverses, exactement comme le faisait le conteneur EJB d'un serveur d'application JEE dans notre exemple précédent. Pour cette raison, on qualifie Spring de conteneur léger (léger car le mécanisme de fonctionnement est beaucoup plus simple et rapide que celui d'un container EJB soumis à d'autres contraintes, en particulier le support des appels distribués par la technologie RMI). Je décrirais la façon dont Spring interfère sur le comportement des classes dans un second article consacré plus spécifiquement à l'AOP.

Pour en finir avec la parenthèse sur la comparaison JEE/Spring a laquelle m'a conduit cette explication de l'IOC, je rappelle simplement que JEE a complètement changé son fusil d'épaule et s'appuie sur l'injection de dépendance (et l'AOP) depuis la version 6 de la norme ; il n'y a plus aujourd'hui de grosse différence en terme d'IOC entre Spring et JEE.

Voilà, un très long article pour expliquer un truc tout simple. Mais j'ai tellement entendu d'absurdités sur le thème IOC/DI (Dependency Injection) que ça me semblait pas totalement inutile.

Framework

Le concept objet est simple, le langage Java est simple, les APIs de base ne sont pas pire qu'avec tout autre langage (encore que, java est un peu lourdingue il faut bien le dire). Mais il faut encore avoir un bon niveau de connaissance des design pattern classiques, et beaucoup d'expérience pour faire un développeur objet expert.

De ce fait, les vrais experts sont rares et chers. Par ailleurs, les technologies objet sont très présentes en particulier à cause de la très large implantation de JEE dans les grandes entreprises (qui ont les moyens de sa mise en oeuvre), et il y a une très forte demande en développeurs dans les SSII.

Heureusement, les frameworks sont là pour simplifier les développements et masquer une grande partie de la complexité, réduisant ainsi la nécessité de recourir à des experts pointus.

Du coup, il y a un déplacement du centre de gravité des compétences : on cherche souvent plus un développeur formé et directement opérationnel sur tel ou tel frameworks (généralement plusieurs) qu'un expert pointu (même si bien sur l'un n'empêche pas l'autre).

Bien, mais c'est quoi un framework ?

La notion de framework est liée au l'idée d'IOC. En gros, un framework c'est un programme qui gère une problématique donnée sur tous ses aspects invariants, qui garde le contrôle, et qui sait passer temporairement le contrôle au programme qui s'appuie sur lui, afin de le laisser prendre en charge les aspects spécifiques. En reprenant mon exemple de l'écran de saisie avec 3 zones, le framework serait la partie du système qui se charge d'exécuter l'écran et de gérer le déplacement de zone en zone, le clavier, la souris etc., toutes choses dont le développeur est déchargé.

Martin Fowler, un des 4 du GOF dont j'ai parlé plus haut, a écrit un excellent article sur le sujet, j'en reproduis une partie très éclairante ici :
Inversion of Control is a key part of what makes a framework different to a library. A library is essentially a set of functions that you can call, these days usually organized into classes. Each call does some work and returns control to the client.
A framework embodies some abstract design, with more behavior built in. In order to use it you need to insert your behavior into various places in the framework either by subclassing or by plugging in your own classes. The framework’s code then calls your code at these points.” (Fowler, Martin. Inversion of Control. 2005.)

J'espère que la notion de framework est à peu près claire. Les frameworks occupent une grande partie de la boite à outil des développeurs, elle accroît leur productivité en masquant la complexité et en mâchant une partie importante du travail. Les autres outils sont les libraires (ou APIs) qui sont des collections de code tout écrit qui résolvent des problèmes courants, et les divers outils utilisés pour lé développement (éditeur de code, debugueur pour la mise au point, compilateur, outil d'automatisation des tests, outil d'automatisation des chaînes de build etc.).

On a des frameworks web focalisés sur la problématique de navigation entre pages, de génération de pages html mixant sur code statique de présentation et des données en provenance de la base de données, de gestion de la session ; on a des frameworks de persistance focalisés sur l'optimisation des lecture et écriture en base de données, on a des frameworks spécialisés sur les problématiques de sécurité (authentification, habilitation, sso...) ; on a des frameworks de test focalisés sur l'écriture de programme qui testent les programmes (oui oui vous avez bien lu) ; bref, il en existe énormément.

Mais tout n'est pas rose, il y a des effets pervers à l'utilisation de ces outils qui mâchent le travail. Le premier souci est que beaucoup de développeurs ne développent plus un niveau très élevé de maîtrise de la POO (d'où bien des problèmes). Comme bien souvent quand on utilise des outils évolués, les développeurs en deviennent dépendants et finissent par ne plus savoir ce qui se passe sous le capot ; ils sont incapables de se débrouiller sans leur outil magique : qu'il atteigne ses limites et ils trouvent les leurs, ils ne savent plus rien faire par eux mêmes et méconnaissent les API de base. Le second souci est que ces frameworks sont souvent largement surdimensionnés pour des problématiques simples, et du coup, si on laisse faire les développeurs, ils emploient souvent un marteau (et bien souvent une caisse à outil complète) pour écraser une mouche, avec des conséquences sur l'empreinte mémoire, les temps de build, et surtout les pré-requis pour la maintenance (la maîtrise d'un framework s'acquiert elle n'est pas innée ...).

Pour cette raison, on a différents niveaux d'intervenants dans la profession, et on trouve désormais souvent des développeurs qui ne sont guère que des exécutants avec un faible niveau de qualification (enfin tout est relatif), qui travaillent dans un cadre le plus étroit possible défini en amont par des experts qui maîtrisent les concepts objets et ont une connaissance pointue des frameworks et modèles d'architecture applicative. Le développement du recours à l'offshore (envoi des développements à l'étranger dans des pays à faible coût de main d'oeuvre, avec aussi un niveau de compétence et de formation inférieur à celui de la France, je dis ça j'ai rien dis...) va également dans ce sens.

Voilà pour les réflexions philosophiques sur l'organisation du travail dans notre merveilleux monde libéral.

Conclusion temporaire

Mon objectif ici était double :
  • dégonfler tout le bullshit autour de notions techniques simples montées en épingle par un marketing peu inspiré (encore que, il faut reconnaître que ça a bien marché, ce qui n'est pas très rassurant en soi)
  • expliquer le plus simplement possible ces notions afin d'en faciliter la compréhension


Il reste encore quelques points aborder, mais ce sera dans un second article, celui ci me semble assez long déjà ;-)