Adam Bien qui est passé au Paris JUG, a un blog de qualité qui contient des articles intéressants. Voici une traduction et une discussion basée sur les commentaires des contributeurs. Si vous voulez lire l’article et les commentaires, je vous recommande chaudement de lire le blog d’Adam.
Traduction de l’article d’Adam Bien
Il est fréquent de voir dans des projets la « bonne pratique » suivante »Service service = new ServiceImpl();En bref : pour chaque interface il y a une implémentation appelée « Impl ». Ce n’est pas tout, parfois l’ensemble est configuré dans un fichier XML (sans doute pour mettre en avant l’importance de ce pattern:-))
Ce n’est pas que gonflant (et opposé à l’idée de « convention par dessus de la configuration ») mais cela peut causer de réels dommages :
1. Que faites-vous si vous avez ensuite une 2ème implémentation (l’intérêt d’une interface), comment allez vous la nommer ?
2. Cela double le nombre d’artefact et peut augmenter la complexité
3. Lire la javadoc entre l’interface et l’implémentation devient marrant, stupide ou pas à jour…
4. La navigation dans un IDE est moins fluideJ’ai demandé à des développeurs pourquoi ils écrivent ce code, je n’ai pas eu de réponses raisonables, à part « pour les tests » (because of mocking).
Attention, c’est complètement faux depuis de nombreuses années (ndlr : en effet Mockito commet EasyMock avec un module en plus sont capables de mocker des classes non finales).Vous avez une idée ou une raison pour continuer à le faire ?
Ce que j’en pense
Premier réflexe : YAGNIT (You ain’t gonna need it -> tu n’en n’auras pas besoin) est un bon principe qui vous rappelle qu’en programmation, il ne faut pas prévoir avant d’avoir un cas d’usage sous les yeux. Le pire travers d’un développeur est d’avoir de grands principes sans savoir exactement quand les appliquer.
Lorsque vous développez une architecture service, un architecture type RPC à plusieurs couches, il est intéressant de séparer l’implémentation de l’interface. Cependant, si vous prenez une classe toute simple, vous pouvez très bien déclarer les méthodes à cacher en private, cela marche aussi non ? Vous pouvez aussi utiliser une Facade afin de masquer vos choix d’implémentation par exemple. Ou encore le pattern Strategy par exemple. Bref il existe différents moyens d’architecture pour construire une application avec différentes couches. Proposer une séparation avec une interface et une implémentation n’est pas toujours le pattern le plus approprié. En fait par expérience, je me rends compte que cela donne des architectures très « application de gestion » avec une vue centrée sur les tables, au détriment de la vraie architecture. Je crois que le pire était dans l’architecture des EJB 2.1. En raison d’un choix technique (l’approche RPC avec sérialisation) il y eut de nombreux projets avec une approche purement technique, sans réelle architecture.
Et vous ? Que faites-vous dans votre code ?
0 no like
Je suis bien d’accord, les interfaces sont un peu trop systématisées. Surtout si on instancie soi-même l’implémentation… Bon, si en plus elle est unique, ça devient vraiment ridicule.
Pour les raisons de ne plus le faire, je ne suis pas vraiment d’accord par contre :
1- si c’est pour le mocking, la deuxième implémentation s’appellera ServiceImplMock. Bon c’est pas super, je suis d’accord, mais ca n’a rien de délirant non plus.
2- Cela double le nombre d’artifact, en effet, de là à ajouter de la complexité. C’est un peu rapide comme conclusion. Je suis preneur d’un exemple de « complexité » supplémentaire
3- Quand on implémente, la doc se trouve dans l’interface. C’est elle qui edicte le contrat, c’est elle qui contient la doc.
4- Pas avec Eclipse et le plugin Implementors
Bref, je suis d’accord avec lui mais ses raisons ne sont pas toujours vraies. En fait, c’est beaucoup plus simple pour moi : il ne faut pas faire d’interfaces qui ne servent à rien parce que… elles ne servent à rien. 🙂
En faisant des revues de code, je vois souvent cela en ce moment:
@Autowired
private IMonSuperServiceMetierADecouplerAToutPrix service;
avec l’interface packagée dans un jar, une implém en XxxImpl et bien sur le mock MockXxx 😉
Malheureusement Spring a poussé les développeurs à utiliser les interfaces à fond même quand cela n’est pas justifié, et comme dans la conf XML ou avec les annotations il est plus facile de déclarer/injecter un bean en passant par le constructeur/setter plutôt que parfois passer par des fabriques, façades ou autres, on a perdu au niveau design, dommage!
Merci les couches et les passe-plats sans valeur métier…
MERCI !
Merci pour ce billet, franchement. Je me sens un peu moins seul.
Je vois sans arrêt partout autour de moi dans ma boîte des gens qui veulent absolument appliquer ce pattern. Systématiquement. Pour tout. Une interface, une implémentation, dont l’instance est créée par Spring. Même s’il n’y a qu’une seule implémentation. Même si on se doute bien que dans 2 ans il n’y en aura toujours qu’une seule. Et on me regarde avec des gros yeux ronds quand j’émets des réserves sur cette manière de procéder, systématique.
De mon point de vue, je crois vraiment que la raison de cette manière de faire, en tout cas chez moi : c’est Spring.
Les gens veulent mettre du Spring (IoC) partout, à toutes les sauces, donc par extension créer tous leurs objets de cette manière, et avoir dans la foulée une interface pour tout (« au cas où »).
Moi je trouve juste que çà fait perdre beaucoup en lisibilité / en temps de configuration / en temps de débuggage niveau XML et que çà ne devrait être réservé qu’aux situations qui l’exigent (et de toutes façons, Google Guice c’est vraiment mieux que l’IoC de Spring, d’abord).
(voix d’Omar et Fred du Service Apres-vente des Emissions) » Roooh tu critiques Spring ! tu vas avoir de gros problèmes toi ! ils vont te retrouver et t’inverser les controles. Ils vont te fumer ! roooh »
Le principe de programmer vers une interface et non une implementation est un des principes de base de la conception objet et c’est le but premier des frameworks d’injections que de promouvoir cela(low-coupling). Maintenant quand tu créés systèmatiquement une interface alors qu’il n’y a pas de variation d’implémentation, ça craint c’est vrai. Le problème c’est que les programmeurs n’ont pas tous la même expérience….
Moi j’aime bien quand il y a plein de classe partout et qui sont mal nommées et non utilisées. Ça justifie notre temps passé et notre salaire 😉
« convention par dessus de la configuration »
attention au contresens!
il faut traduire « convention plutôt que configuration »
1/ Service est un contrat – ServiceImpl en est une implémentation mais rien ne nous dit que ServiceImpl n’implémente que ce seul contrat…
De ce fait, pourquoi s’encombrer de tous les contrats que ServiceImpl peut représenter quand seul Service est nécessaire dans le contexte ?
2/ il existe des techniques de dissimulation d’interface par implémentation explicite qui peuvent également justifier pleinement cette construction
C’est YAGNI sans t 🙂
Après avoir lu les commentaires de l’article original, je pense que le vrai problème c’est l’utilisation de new.
Cf.
http://misko.hevery.com/2008/09/30/to-new-or-not-to-new/
http://misko.hevery.com/2008/07/08/how-to-think-about-the-new-operator/
La justification « pour les tests » est tout à fait raisonnable, à condition que le service soit effectivement « mocké » à côté.
J’irais même un peu plus loin; pourquoi écrit-on:
Service service = new ServiceImple(); service.maMethode();
et pas directement:
Service.maMethode();
Pourquoi utilise-t-on des singletons plutôt que des méthodes statiques ? La seule justification raisonnable que je connaisse est également : « pour les tests ».
La justification « pour les test » me semble aussi tout à fait justifié pour la couche service : parfois l’implémentation de référence n’est disponible qu’après le début du dev. : il faut bien bouchonné en attendant et aussi pour les tests unitaires. L’approche par interface est tout de même plus propre que d’instrumenter un bean avec un framework type Mockito.
Dans tous les autres cas effectivement cela ne se justifie pas. D’ailleurs Spring est capable d’injecter des implémentations directement donc il n’y a pas de problème …
Même sans Spring, on peut trouver des trucs sympa du genre :
interface Service { ... } @Local interface ServiceLocal extends Service { // Rien du tout, on ne fait que de l'héritage } @Remote interface ServiceRemonte extends Service { // Rien du tout, on ne fait que de l'héritage } @Stateless class ServiceImpl implements IServiceLocal, IServiceRemote { ... }
Très facile à lire et à naviguer dans l’IDE…
Par défaut je n’utilise pas d’interfaces (si j’ai besoin de plusieurs implémentations, alors je refactor mon code et j’en introduit) et je n’utilise pas de DAOs (idem, lorsque l’entity manager et les named query ne suffisent plus, alors je refactor mon code et j’introduit des DAOs).
Vous pouvez donc toujours chercher des DAOs avec interfaces dans mon code ;o)
Une utilisation récente et concrete d’interface a été quand pour un cas très particulier j’ai du introduire de l’AOP avec Spring AOP pour un comportement ou je ne voulais pas toucher à mon code mais je voulais juste utiliser Spring AOP. Spring AOP c’est super simple à utiliser et ca marche que pour les classes avec interface. Après on peut toujours utiliser aspectj, mais c’est plus compliqué (pour moi).
Voila pour un exemple concret. Apres je suis bien d’accord pour l’aspect javadoc surtout… A la longue, je finis par mettre la javadoc à jour dans l’implémentation.
Excellent billet que j’aurais aimé écrire comme on dit 🙂 J’étais parti du même constat lors de la conception de l’API Restlet. Malgré de nombreuses demandes de séparation, j’ai pour l’instant ‘resisté’ malgré le sentiment d’aller contre les règles établies.
Les interfaces ne sont utilisées que rarement, lorsqu’elles sont limitées à une seule méthode très ‘ouverte’. Pour les APIs publiques, les interfaces sont d’ailleurs d’autant plus difficile à maintenir car l’ajout de la moindre méthode risque de casser les implémentations existantes chez les utilisateurs. Les classes en revanche autorisent des comportements par défaut.
Mais c’est une question encore soulevée fréquemment (souvent par les Springers et les Mockers en effet) donc j’aurai quelques argument en plus la prochaine fois! 😉
Merci Nicolas pour ce post et le débat qu’il lance !
Moi aussi, j’étais rétissant à toutes ces interfaces, je trouvais que ça démultiplier les fichiers sources, le « suivi » dans le code est plus difficile, pas facile de s’y retrouver, de comprendre, etc …
Mais à force de lire du code avec des architectures basées sur ce principe, j’y adhère de plus en plus. Et je m’aperçois finalement que certaines difficultés citées ci-dessus sont en fait plutôt liées à des habitudes … 😉
Je reprends quelques arguments évoqués.
* lorsqu’il y a différentes implémentations : là, je crois que nous sommes tous d’accord
* Javadoc : je n’en mets (presque) plus partant du principe que la lisibilité du code doit suffir et sera toujours à jour (contrairement à la javadoc rarement maintenue …)
* « suivi » dans le code : dans Eclipse, CTRL+T sur la méthode me permet d’aller directement sur la méthode dans une des implémentations, donc ça n’est plus un problème
* tests : je suis un fan de Mockito, je l’utilise dans tous mes tests à partir du moment où la classe testée a au moins un collaborateur. OK, on peut faire des mock() sur des classes, mais je préfère sur les interfaces (et j’ai eu des soucis sur les classes). Et je n’implémente plus jamais de Mock pour les tests. Par contre, il m’arrive de faire des « Fakes », pour fonctionner en « run » en attendant la « véritable » implémentation, et là, une interface est la bienvenue
* IoC : oui, bien sûr, pattern évident. Mais je ne vois pas le rapport avec Spring qui permet de l’Ioc entre classes (= implémentations), et on peut concevoir en IoC sans Spring … 😉
* et tout simplement sur le fond, perso, je trouve que ça clarifie les contracts. Comme le dit @Franck, le couplage lache est important et permet de bien distinguer les rôles et liens entre les classes. Mais bien sûr, pas forcément à utiliser à 100%, par exemple en interne dans un package, i.e. dans un périmètre très limité, l’interface peut être abusive …
@christophe « 2/ il existe des techniques de dissimulation d’interface par implémentation explicite qui peuvent également justifier pleinement cette construction » : tu peux en dire plus STP ?
Xavier
@Xavier
dans ton resume, tu oublies mon argument 1/ que je crois important car se rapporte au principe de segregation d’interface
concernant la technique que j’evoque en 2/ elle est plus connue sous le nom de « interface hiding with explicit implementation », tres utilisee en c# (mon absence de notions en java ne me permet pas de t’en donner l’equivalent)
une explication ici : http://craz.net/blog/2008/06/25/explicit-interface-implementations-in-c/
Je ne suis pas sûr qu’il y ait vraiment débat en fait (même si les commentaires sont intéressants).
Ca me rappelle les « débats » sur Checked vs Unchecked Exceptions.
L’abus n’est jamais bon, mais quand on a besoin d’un contrat clair ou de fournir des stubs, ou de varier les implémentations (et seulement dans ces cas précis) on utilise une interface, voilà tout. Dans tous les autres cas, on ne le fait pas.
Les interfaces fournissent aussi un bon pattern pour ajouter des comportements génériques à des classes, et ensuite effectuer des opérations transverses (par AOP ou non). Du genre implements UniquelyIdentifiable pour les classes qui implémente une méthode getUniqueId()… (ok pas l’exemple ultime, mais bon… 😉 )
Dans le cas précis du débat lancé, il me semble que c’est surtout une attaque soft sur le modèle de programmation Spring par un partisan de JavaEE 6 (et c’est de bonne guerre). Ne soyons pas dupes.
Chaque solution logicielle vient avec son lot d’idées préconçues.
On avait déjà rigolé sur ça, mais je ne suis pas sûr qu’avoir une implémentation du genre:
@Role(ADMIN)
@Scalable
@DeployOnCloud
@NamedRequest(MY_REQUEST)
@EJB
@Singleton
@PrepareCoffee
@RememberTheMilk
public void doWhateverYouNeedToDo(){
…
}
renseigne beaucoup plus l’appelant sur ce que va faire le code qu’il invoque.
Et je moulte-plussoie le fait que la JavaDoc n’est nécessaire que quand les méthodes sont mal nommées, et quand les paramètres des méthodes sont des listes de types primitifs (beurk).
Conclusion (si c’est nécessaire): tout est bon dans le cochon, mais si on en abuse => crise de foie. (je vais me soigner maintenant ;))
Depuis le temps que le disais à mes collègues! Quel bonheur quand j’ai rencontré Adam Bien au JUG. Honnêtement Service s = new ServiceImpl() c’est limite le signe d’un code qui n’est pas orienté objet, ou le code utilise presque uniquement la propriété de namespace du langage.
Ce code là va souvent de pair avec des JavaBean, en gros des structures avec des accesseurs, …seulement!
Heureusement Java EE 6 est là, mais avant que l’ensemble des projets bouge il y en a encore pour 10 ans. Aujourd’hui je suis sur une mission Struts 1.x, Spring 2.x, et des anti-patterns de partout, dont celui de cet article, et ce n’est pas près de changer chez eux 🙁
Et si le problème c’était que ce qu’on dénonce ici comme « worst practice » était ce qui fut, il y a peu, présenté comme une « best practice » et que si tu ne le faisait pas tu passais pour un has been de la programmation ?
La vitesse à laquelle le monde Java est capable de mettre sur un pied d’estale puis de lyncher les mêmes choses me surprendra toujours 😉
@HinkyMinky
Ouai ce que tu dis m’a traversé l’esprit. Mais en fait ce n’est pas seulement lié à Java et ce qu’on appele computer science n’est pas vraiment une science exacte et c’est une science qui évolue sans cesse par ce qu’on apprend de nos erreurs.
A l’époque ou les goto ont été déclarés comme harmful par Dijkstra alors que la pratique était très courante, il y a eu beaucoup de résistance, etc.
Ce qui à l’époque était donc une bonne chose de mettre des interfaces partout était en partie du a des limites techniques, et en partie du à une vision conceptuelle/architecturale sur-compliqué. Honnêtement pour moi c’est un design smell de faire « Service s = new ServiceImpl() », après effectivement il ne faut pas bannir l’interface, au contraire même, mais il faut les utiliser lorsqu’il y a un vrai besoin comme le pattern strategy, ou encore les interfaces granulaires comme Closeable, etc.
Les développements Service s = new ServiceImpl(); ne devrait être utilisé que lorsque l’interface est exposée à d’autre modules (client de service, utilisateur d’API) pour éviter les import ServiceImpl;, mais dans un même module une simple implémentation suffit la plupart du temps.
Il ne faut pas faire de new XxxImp()… il faut se faire injecter l’implémentation.
L’implémentation peut varier quand on utilise Spring.
Par exemple quand vous utilisez @Transactional ou d’autres merveilles de spring sur l’implémentation, Spring enrobe votre implémentation (crée un proxy) et injecte son implémentation. C’est plus pratique quand une interface est présente. C’est quand même ultra-puissant non ?
Le « developpement par interface » est conseillé pour le « separation of concerne », c-a-d éviter le couplage fort.
Un pojo n’a pas a savoir ce qu’un autre pojo fait, il se fiche de son implementation, ou il devrait. C’est pour cela qu’un pojo devrait toujours n’utiliser qu’une interface et donc ne declarer leurs attributs que par interface. Suivre cette règle permet en général d’avoir un meilleur design, de façon naturelle (c-a-d sans avoir trop à y penser).
Enfin cela parle plus au gens qui font de la dependency injection (désolé de mes anglicismes) avec notamment spring ou guice.
D’ailleurs aujourd’hui les gens serieurx font du DI. Allez un peu de provoque 😉