De mon point de vue, le principal problème avec la programmation orientée objet, c’est qu’elle est mal comprise.
La conséquence, c’est qu’elle est mal utilisée (y compris au sein de langages qui sont censés la mettre en valeur), mal enseignée, et qu’on essaie de faire tout et n’importe quoi avec. Alors que c’est un outil, et que comme tout outil, la POO a un cadre d’utilisation, et l’utiliser hors de ce cadre est au mieux non optimal.
À propos de la POO
C’est quoi la POO ?
En programmation orientée objet, on a des composants de base qui sont les objets (merci Captain Obvious). Les objets ont un état interne et un comportement qui leur permet d’interagir entre eux.
Et… c’est tout.
La conséquence immédiate de tout ça, c’est que la phase de conception est primordiale, parce que c’est la définition correcte des objets et de leurs interactions qui va permettre de faire fonctionner le programme.
Ce que n’est pas la POO
Y’a plein de trucs qui sont souvent considérés comme « indispensables à la POO » alors qu’en fait pas du tout. Comme les classes, le typage statique et/ou fort, les interfaces, etc.
Pourquoi la POO ?
La POO permet à l’origine de répondre à deux problèmes :
- La réutilisation « simple » du code (via la réutilisation des objets)
- Permettre le découplage entre le fonctionnement interne d’un objet et son comportement : si on respecte les contrats de comportements, on doit pouvoir changer tout le fonctionnement interne de l’objet sans conséquence fonctionnelle.
La recherche de performance, par contre, n’est pas une priorité.
Et donc, c’est quoi le problème ?
« Quand tu as un marteau, tout ressemble à un clou »
La POO devrait être utilisée (et devrait n’être utilisée que) lorsqu’on a un problème qui peut se traduire en objets qui interagissent entre eux.
Mais comme dit le proverbe, « Quand tu as un marteau, tout ressemble à un clou », et donc la tentation est grande de créer des objets très artificiels, qui n’ont à peu près aucun sens, juste pour « faire de la POO » (quelle qu’en soit la raison à l’origine).
Alors qu’en fait il faudrait limiter l’usage de la POO aux parties du code pour lesquelles c’est pertinent, et utiliser d’autres paradigmes pour gérer le reste. Le problème, c’est que ça n’est pas toujours facile parce que…
Des langages qui font n’importe quoi
On a un looong historique de langages « orientés POO » qui ont fait n’importe quoi avec. Soit comme PHP < 5.3 qui prétentait permettre d’en faire alors que non, soit comme Java (surtout < 8) qui forçait en pratique à en faire là où ça n’était pas utile.
Si je prends l’exemple de Java que je connais le mieux (vu que je bosse avec depuis plus de 10 ans), on peut noter en vrac :
- Tous les types natifs qui ne sont pas des objets, mais qui ont des autoboxing / autounboxing vers des classes équivalentes pour pouvoir les considérer comme tels (avec tous les pièges et comportements bizarres que ça peut entrainer)
- Des chaines d’héritage qui n’ont aucun sens et qui conduisent à des comportements aberrants
- L’obligation de déclarer une classe pour tout et n’importe quoi, y compris ce qui n’est de doute évidence pas orienté objet (coucou la fonction
main
).
- Des objets planqués sous le tapis (dans les
Enum
par exemple)
Et là je parle bien du langage lui-même, et pas de l’utilisation qui en est faite.
Un enseignement complètement à la rue
L’immense majorité des cours de POO n’expliquent pas ce qu’est la POO : ils expliquent une vision défectueuse de la POO. Généralement en lui prêtant des buts et des moyens qui ne sont pas les siens, ce qui conduit à une mécompréhension générale de l’outil.
Le pire étant les exemples donnés, qui sont très souvent :
- Mauvais (entre autres parce qu’ils ne définissent généralement que l’héritage et pas les autres formes d’interaction comme la composition)
- Impossibles à mettre en pratique dans un vrai programme, ce qui n’aide pas à comprendre. Sérieusement, qui a besoin de représenter « un chat » ou « une voiture » sous cette forme dans un programme ?
- Le simple fait de prétendre que tout problème peut être résolu par la POO. Je ne compte même plus les exercices où on exige de l’élève qu’il utilise la POO pour résoudre un problème où ça n’a aucun sens.
Des pratiques habituelles complètement cassées
Tout ça conduit à des pratiques habituelles considérées comme « de la POO » (voire imposées en entreprise) qui n’ont en fait rien à voir avec la POO, et qui sont totalement contre-productives. En vrac :
- Cette manie de mettre des getters et setters partout. Ça expose l’état interne de l’objet et casse complètement la notion d’état interne (donc non exposé à l’extérieur). Un getter ou un setter ne devrait être créé que si c’est pertinent, et ne pas correspondre obligatoirement à un état interne réel.
- Tout faire par héritage. Il y a des cas où ça n’a juste pas de sens, la composition c’est bien aussi.
- Les piles de
instanceof
pour déterminer le comportement à appliquer en fonction du type de l’objet. Ça déporte la logique dans le code appelant, au lieu de laisser les objets réagir correctement à leur manipulation.
- Le classique « il doit impérativement y avoir des interfaces » (et ses petits frères, les Factory etc). Là c’est plus qu’on se rajoute du travail inutile sous de faux prétextes.
Et donc, on fait quoi ?
L’idée, c’est d’utiliser un outil adapté à chaque problème, et donc plusieurs paradigmes au sein d’un même programme. La plupart des langages modernes sont d’ailleurs multiparadigmes et assez souples là-dessus. Idem avec les évolutions modernes de langages anciens, comme Java qui a ajouté une bonne dose de fonctionnel depuis Java 8 – et dans les cas où c’est utile, comme les traitements de séries d’objets, c’est très appréciable.
Il y a deux inconvénients à ça :
- Il faut maitriser plusieurs paradigmes différents en même temps et être capable de changer facilement selon le contexte, ce qui n’est pas facile.
- On voit arriver de nouveaux problèmes. Comme cette mode qui veut que l’on ne crée plus que des objets immutables, ce qui est très pratique pour les traitements fonctionnels et peut sensiblement améliorer les performances… mais qui est contraire à la définition d’ « objet » dans la POO. Et donc appeler ça des « objets » devient abusif, casse les parties de code où ils sont utilisés dans le paradigme de POO, et engendre encore plus de confusion.