Les défauts de la Programmation Orientée Objet
La programmation orientée objet a été la grande évolution dans le domaine de la programmation informatique et des langages. Elle a introduit des concepts innovants et faciles d’accès qui ont séduit de nombreux développeurs. Cependant, avec un peu d’expérience, il en ressort des difficultés qui font que la programmation objet en tant que telle peut être un frein dans la construction d’applications, et risque éventuellement dans les années à venir de laisser sa place à d’autres techniques.
On peut facilement concéder à la programmation orientée objet plusieurs atouts. Le premier est marketing : modéliser l’ensemble d’un programme par des objets tels que ceux qui nous entourent est un concept attirant, à portée d’esprit voire même palpable. Cela permet normalement un apprentissage plus aisé de la programmation, en comparaison avec d’autres types de programmation comme la fonctionnelle, pour ne citer qu’elle. Un deuxième atout est la grande part de détection des erreurs à la compilation : les langages Java et C++, par exemple, se sont dotés de compilateurs performants qui, grâce à un typage fort des objets et à tout un tas de notions (héritage, visibilité…), permettent au développeur de corriger une partie du code très en amont. Le dernier atout est une certaine proximité avec les langages bas niveau : l’appel d’une méthode en programmation objet peut se traduire assez intuitivement en C ou Assembler. La réécriture et l’utilisation d’un code bas niveau en code objet se réalisent plus simplement qu’avec d’autres types de programmation. Je passerai sous silence les avantages procurés par le succès de la programmation orientée objet et du langage C++ en particulier, ce qui a amené au fil du temps un certain nombre de bibliothèques et de solutions conceptuelles.
Venons-en aux problèmes que l’on peut rencontrer lorsque l’on programme en objet. Je l’ai dit plus haut : le concept est pour moi purement marketing, et n’apporte pas de vraie solution pratique pour la programmation. En effet ce qui caractérise le monde, ce qui le fait vivre et évoluer, ce n’est pas tant la description des objets qui y habitent mais plutôt - et la notion est importante - les interactions entre ces objets. Depuis des années les modèles objet essaient de proposer des solutions plus ou moins bonnes pour répondre à ce manque.
Lorsque l’on travaille avec des bases de données, par exemple, un schéma souvent rencontré consiste en 3 niveaux : des objets qui représentent les informations et qui ne sont décrits que par des attributs, des objets permettant de communiquer avec la base de données, et enfin - et c’est là où je veux en venir - une couche d’objets qui permettent de faire la liaison entre la base de données et les objets du langage. On a besoin ici de traducteurs qui permettent de faire passer les informations du langage vers les informations de la base de données (classiquement de traduire un objet en langage SQL, et inversement). Ces objets-traducteurs n’ont pas d’existence réelle, ils n’ont pas de légitimité en tant qu’objets mais plutôt en tant que “méthodes”. Ils représentent ainsi en quelque sorte une limite à la programmation orientée objet.
Un modèle de conception fréquemment utilisé est celui de l’Observateur. Il permet à un objet observé de notifier un évènement à des objets observateurs, pas forcément connus de l’objet observé (dans le sens où l’observé ne sait pas qui l’observe et quand) ; il s’agit typiquement de provoquer des actions asynchrones : appel à une méthode lorsqu’un utilisateur clique sur un bouton, lorsque des données arrivent sur un flux, lorsqu’un attribut spécial est modifié… Ce modèle est implémenté de manière très sommaire dans les langages orienté objet : l’objet observé enregistre des objets observateurs par le biais de deux méthodes (ajout et suppression), puis quand une action spéciale est effectuée il parcourt la liste des observateurs et appelle une méthode particulière, généralement définie dans une interface spécifique à chaque type d’observateur. On ne peut s’y tromper : ce système est particulièrement lourd à mettre en place et comporte de nombreux défauts. Pour pouvoir observer les modifications sur un attribut par exemple il est obligatoire que l’objet qui possède les fonctions de modification de cet attribut mette en place tout un système d’observation (gestion de la liste et appel de la méthode sur chaque observateur à chaque modification de l’attribut). Il est donc limité à ce que le développeur d’un objet a bien voulu écrire. Toutes les difficultés de mise en place et d’utilisation de ce système peuvent être constatées lors du développement d’une application avec interface graphique en utilisant le modèle Modèle-Vue-Contrôleur (MVC) : un contrôleur est observateur des actions utilisateur sur la vue, elle-même observateur des modifications sur les données du modèle, modifications initiées par les contrôleurs.
Un autre problème important de la programmation orientée objet se dégage lors du développement d’applications qui se doivent robustes et ayant le moins de bugs possible. Il s’agit de logiciels utilisés dans des domaines critiques : santé, aviation, gestion de centrales éléctriques, etc. Le concept objet ne donne pas en soi de solutions qui permettent de spécifier des contrats que le programme doit respecter. Les contrats déterminent les règles que doit respecter un programme pour qu’il soit jugé correct en spécifiant pour les méthodes dans quels domaines ils ont été écrits et les invariants qui doivent subsister entre les attributs. On va par exemple pouvoir indiquer qu’une méthode n’est valide que pour un certain domaine d’entrée (les entiers positifs inférieurs à 100) et qu’un attribut doit toujours être supérieur à un autre. Le programme va se charger de vérifier que les règles sont respectées à la compilation et pendant l’exécution, et ceci quel que soit le contexte d’exécution (classiquement en multi-threads). La spécification de ces règles est actuellement très complexe en programmation orientée objet et la diffusion d’objets respectant des contrats n’est pas facilitée par des automatismes en matière de compilation et de documentation.
Dans le même genre, la programmation orientée objet souffre, malgré les bibliothèques existantes en la matière (nUnit, jUnit…), d’un manque d’assistance lors des phases de test. La spécification de tests de manière plus abstraite permettrait de gagner du temps dans leur rédaction et également d’augmenter de manière significative leur qualité. Actuellement, la rédaction de jeux de tests valides et fiables s’avère parfois plus longue et complexe que la conception de l’application en elle-même. De plus, le code d’un jeu de test n’est en général pas exempt de bugs. Il devient donc difficile d’être sûr à 100% qu’un programme répond à sa spécification. Par extension, on peut également reprocher aux langages orientés objet un manque d’abstraction dans le débuggage. Il s’agit en général d’afficher les variables ou d’exécuter le programme pas à pas, ce qui rend la détection de la source de l’erreur longue et fastidieuse.
Un aspect à la popularité grimpante semble également faire défaut à la programmation orientée objet dans le sens où elle ne fournit pas de solutions simples à comprendre et à utiliser : la synchronisation. Cette idée met en évidence les difficultés immenses qui apparaissent obligatoirement à terme dans le développement d’une application complexe : la gestion des exécutions en parallèles de parties de l’application. Si les langages objets sont pour la plupart dotés de systèmes de synchronisation (création de threads, gestion des files d’attente, sémaphores, verrous…), il n’en reste pas moins que ceux-ci sont bien souvent un casse-tête pour les concepteurs : quelles opérations peuvent être parallélisées ? quelles variables peuvent être modifiées simultanément ? comment assurer que telle ou telle opération va être exécutée avant une autre ? y’a-t-il un risque d’interblocage ? Pour l’instant, en programmation orientée objet, le concepteur doit mettre la main à la pâte en ayant en tête que le débuggage d’applications multi-threadées n’est pas une mince affaire.
Pour terminer, un dernier concept difficile à mettre en œuvre en programmation orientée objet est celui de l’extensibilité. De nombreuses applications ont vu le jour et dans lesquels la notion d’extensions (plugins) est très présente : les produits Mozilla, Eclipse… C’est à coup sûr une des raisons du succès de ces logiciels. Cependant, la programmation orientée objet n’offre pas de solution clef en main pour construire des applications extensibles. Il faut alors recourir à tout un système plus ou moins complexe qui fait souvent intervenir des opérations de réflexion au sens programmation, c’est-à-dire d’analyse de la structure des classes.
Quelques pistes de solutions commencent à émerger pour contrer ces manques majeurs de la programmation orientée objet. Le langage C# fournit par exemple des réponses syntaxiques à qui permettent de gérer plus facilement un système d’observateurs. Les concepts de programmation par aspect et de programmation par contrat font leur entrée et peuvent être intégrés à des langages objets. Java bénéficie par exemple des bibliothèques AspectJ et jContract. Cependant, il s’agit principalement d’astuces syntaxiques ou conceptuelles, voire de surcouches du langage, et il subsiste donc des soucis en terme d’efficacité mémoire et de performances.
La programmation orientée objet a donc un côté très attrayant pour le développeur : elle bénificie d’une popularité gigantesque et d’un concept simple et accrocheur. Malgré tout, elle doit évoluer vers de nouvelles formes de programmation afin de répondre à des exigences de plus en plus fortes, notamment au niveau de la création d’interfaces graphiques et d’applications multi-threadées ou distribuées.