Débat sur un principe de POO : la Loi de Demeter
. Quelles méthodes peut appeler un objet ?
Le 2009-10-14 17:51:47, par 3DArchi, Rédacteur
Bonjour,
Ceci fait suite à cette discussion .
On peut trouver une définition assez complète de la loi de Demeter dans ce billet d'Emmanuel Deloget.
En résumant, l'idée de la loi est de dire que dans une méthode m d'une classe A, je ne peux utiliser que :
-> les méthodes de A (et ses classes de bases) ;
-> les méthodes des membres de A ;
-> les méthodes des paramètres de m ;
-> les méthodes des objets créés par m ;
-> les méthodes des variables globales.
En revanche, cette loi sous-tend qu'on n'a pas de transition : on ne devrait pas utiliser :
-> les méthodes des membres (ou des objets retournés par) des membres de A ;
-> les méthodes des membres (ou des objets retournés par) les paramètres de m.
etc.
L'idée est qu'à travers cette loi, un objet masque à son utilisateur son contenu.
Comme le note le billet, cette règle souffre d'exception : les structures triviales, les conteneurs et toutes sortes de fabriques pour des raisons évidentes.
Pour reprendre les termes du débat :
Souvent, quand cette loi n'est pas respectée, c'est que soit on expose des détails d'implémentation, soit on n'a pas le bon niveau d'abstraction. Et quand on n'a pas le bon niveau d'abstraction, on a l'impression que pour respecter la loi il faut transposer tout ce qui a dans la classe source à l'intérieur de la classe passerelle, ce que à quoi toutes nos fibres de C++eurs s'insurgent.
Pensez-vous respecter cette loi ? La trouvez-vous trop contraignante ? Quand pensez-vous devoir la respecter et quand la violer ? Bref, quel est votre sentiment ?
Ceci fait suite à cette discussion .
On peut trouver une définition assez complète de la loi de Demeter dans ce billet d'Emmanuel Deloget.
En résumant, l'idée de la loi est de dire que dans une méthode m d'une classe A, je ne peux utiliser que :
-> les méthodes de A (et ses classes de bases) ;
-> les méthodes des membres de A ;
-> les méthodes des paramètres de m ;
-> les méthodes des objets créés par m ;
-> les méthodes des variables globales.
En revanche, cette loi sous-tend qu'on n'a pas de transition : on ne devrait pas utiliser :
-> les méthodes des membres (ou des objets retournés par) des membres de A ;
-> les méthodes des membres (ou des objets retournés par) les paramètres de m.
etc.
L'idée est qu'à travers cette loi, un objet masque à son utilisateur son contenu.
Comme le note le billet, cette règle souffre d'exception : les structures triviales, les conteneurs et toutes sortes de fabriques pour des raisons évidentes.
Pour reprendre les termes du débat :
Souvent, quand cette loi n'est pas respectée, c'est que soit on expose des détails d'implémentation, soit on n'a pas le bon niveau d'abstraction. Et quand on n'a pas le bon niveau d'abstraction, on a l'impression que pour respecter la loi il faut transposer tout ce qui a dans la classe source à l'intérieur de la classe passerelle, ce que à quoi toutes nos fibres de C++eurs s'insurgent.
Pensez-vous respecter cette loi ? La trouvez-vous trop contraignante ? Quand pensez-vous devoir la respecter et quand la violer ? Bref, quel est votre sentiment ?
-
mintho carmoMembre éclairé@igor_
Pour commencer, tu fais un racourci (à mon avis) sur la loi de Déméter. Cette loi ne propose pas une solution ("Si A utilise B et que B propose le service X, alors A doit proposer le service X", mais un "diagnostic" ("A ne doit pas exposer B" . La façon doit être réglé ce problème d'exposition des détails internes ne concerne par la loi de Déméter.
En assimilant la loi de Déméter à cette solution (généralement bancale), il est effectivement facile de trouver des exemples qui sont bancales (avec des interfaces qui deviennent inutilement complexes). Mais cela ne rend pas caduque la loi de Déméter.
C'est un autre point discutable de ton raisonnement. Ton exemple d'agent ne proposent pas d'autres rôles que "accéder à des composants internes". C'est une conception objet orientée "composition de données" et pas "rendre des services".
Il est évident que des classes destinées à accéder à des composants internes (par exemple les pointeurs intelligents ou les conteneurs) ne peuvent pas respecter la loi de Déméter. Mais ce sont des exceptions connues à la loi de Déméter.
(Je ne dis pas que tu as tort de concevoir ton agent comme cela. Si tu souhaites créer une lib de système multi-agent par exemple, tes agents seront peut être de simples conteneurs. Mais ce choix d'implémentation fait que la loi de Déméter ne s'applique pas dans ce cas).
Ta remarque est vraiment étrange. En gros, tu dis que tu ne veux justement pas de design souple et qu'en faisant ce choix, la loi de Déméter est inutile... Ben oui, bien sûr !
Les principes de conceptions visent à avoir du code respectant les critères de qualité logiciel (évolutivité, maintenabilité, etc). Si tu veux faire du code qui ne respecte pas ces objectifs, il est évident que les principes visant à obtenir ces résultats sont inutiles. Pour simplifier, ton message serait donc "si on veut faire du code de merde, les principes ne servent à rien". Ok, on est d'accord sur ce point.
Maintenant, on pourrait discuter sur l'intérêt d'avoir du code de qualité ou non. Dans certains cas (prototypage ?), il est possible que la qualité du code n'est pas un point critique. Mais pour du code pro "standard", qui sera maintenu pendant des années, la qualité du code (et d'une bonne conception) est très critique. Et le respect de la loi de Déméter n'est pas sans intérêt dans ce cas.le 27/01/2016 à 12:40 -
koala01Expert éminent séniorLa plupart des bibliothèques (C++, s'entend) de GUI sont particulièrement mal foutues et témoignent, au mieux (mais c'est logique quand on voit leur histoire et l'époque à laquelle leur développement a commencé) d'une mauvaise compréhension du paradigme orienté objet, et, dans quasiment tous les cas, certains principes SOLID (dont le LSP en premier) sont purement et simplement jetés aux orties.
Je ne remets ici absolument pas en question la qualité du travail effectué pour fournir ces bibliothèques, et j'apprécie énormément certaines d'entre-elles. Mais, de manière générale, la conception d'une bibliothèque d'IHM n'est vraiment pas le meilleur exemple à prendre
Comme je l'ai déjà dis:
>Je n'ai pas donné de détails sur les principaux types (Agent, Comportement...) de mon exemple, car cela aurait alourdi inutilement la présentation. J'ai juste dit qu'ils représentaient des concepts important du domaine métier. J'ai peu ou pas parlé >des services propres à la classe Agent pour deux raisons :
> - ne pas perdre le lecteur entre les services directs et ceux indirects
> - les services directs ne sont en soi pas utiles à la démonstration.
> Pour autant, et de manière générale, quand on n'a qu'une vision restreinte d'un problème, il ne faut pas chercher à déduire ce qu'on ne peu pas déduire.
> Et naturellement, il n'y a pas de raison de penser que la classe Agent n'a pas vocation à proposer ses services propres.
Or, ta classe Agent devrait avoir vocation à ... utiliser en interne les services proposés par les composants, de manière à ce qu'elle puisse se limiter à exposer ... que les seuls services que l'on est en droit d'attendre de sa part.
> Et je réagis à l'aspect "orienté donné", que d'autres également semblent attribuer à mon exemple, à tort :
> Dans tout mon article, je parle essentiellement de services, non pas de données ou de propriétés. Cela veut dire ce que cela veut dire : les types de mon exemple du S.M.A. sont des orientés services avant tout.
Tu pars (enfin, comme tu reste très vague sur le sujet, on va partir d'un a priori favorable)sur une approche orientée service pour les composants de ta classe Agent, mais, en voulant absolument faire en sorte que ta classe agent "réplique" tous les services de tous les composants qu'elle utilise, tu agis littéralement de la même manière que si tu suivais une logique orientée donnée. La seule différence est que tu pars du principe d'exposer les services fournis par les composant de ta classe Agent au lieu de donner accès à ces composants.
Mais, au final, cela revient au même : tu penses à ta classe Agent en termes des données qu'elle manipule et non en termes des services qu'elle doit être en mesure de rendre à son utilisateur> Je pense que certains font un amalgame entre architecture ouverte et structure de donnée. Vraiment, ce sont des choses différentre. Que les widjets d'un frameworks visuels soient tous des objets concrets, avec tout pleins de "propriétés", et > copiables pour certains, ne les dispense pas pour autant de rendre de nombreux services, comme le fait de s'afficher. De même, ma classe Agent, pourrait offrir un service affiche().
Mais on est très loin de ce que tu présente dans ton article
Une telle méthode, en soi, ne va pas supprimer l'intérêt de la loi Déméter, puisqu'elle permet justement de respecter ses contraintes.
C'est ce qu'on obtient au final quand essaie d'appliquer Déméter sur une architecture ouverte telle que celle du S.M.A., que je dénonce.
Car, tout ce que l'on attend de la part des agent est... de réagir (correctement) aux ordres que l'on est susceptibles de leur donner. Et donc, les seuls services "dignes" d'être exposés au niveau des agents sont... les ordres auxquels ils doivent pouvoir réagir et éventuellement quelques "aides à la décision" permettant à l'utilisateur de choisir de donner (ou non) un ordre particulier. Et l'utilisateur d'un agent particulier n'a ABSOLUMENT PAS A SAVOIR DE QUOI L'AGENT QU'IL UTILISE EST COMPOSE;
Attention : je ne détaille pas les composantes dont je parle dans mon article, et c'est voulu. Il n'y a aucune raison de penser que, dans l'implémentation, ces types sont des types concrets. Cela sort du cadre du sujet de la loi de Déméter, mais il faut imaginer par exemple que Comportement peut être un type abstrait, géré par un unique_ptr. Cela permet à un Agent de ne pas avoir l'état de son Comportement "figé" (comme tu mentionnes). Les comportements spécifiques des Agents peuvent ainsi être implémentés de manière différente, en toute transparence pour le code de Agent ET client, qui ne manipulent qu'une composante abstraite.
Ces considérations sont hors-sujet, mais je tenais à rectifier ce point.
Certains de ces services peuvent éventuellement correspondre à des services définis "tels quels" dans un composant donné de ton agent, mais cela reste l'exception! autrement, nous ne nous serions pas "cassé le cul" à regrouper différents composants au niveau de la classe Agent : nous aurions simplement utilisé ces composants séparément et "basta".
Si on a décidé de regrouper plusieurs composants au niveau de la classe Agent, ce n'est que parce que l'on s'est bel et bien rendu compte que les différents composants devaient être utilisés conjointement à certaines occasions. Et les services exposés par ta classe Agent ne correspondent en réalité qu'à ... ces "occasions particulières" dans lesquelles les différents composants doivent être utilisés conjointementle 27/01/2016 à 19:52 -
BouskRédacteur/ModérateurOn peut travailler dans la même équipe, sur des pans différents. Je n'en suis pas moins un utilisateur de ta classe, et son implémentation je m'en cogne tout autant que ce que tu appelles "utlisateur final".
Mais on s'en moque que ton Agent possède un Comportement, But ou quoi que ce soit. Un Agent je lui demande doTask(); et qu'il utilise un Comportement interne ou autre n'est pas du tout mon souci.
La grosse majorité des "services indirects" ne sont que des détails d'implémentation utilisés en interne pour réaliser le service demandé à l'Agent.
Reprends ton exemple de secrétaire, en poussant plus loin l'absurdité
- tu veux appeler ton médecin, qui a plusieurs secrétaires (on va partir de là pour simplifier, on sait qu'il a X secrétaires)
- tu dois récupérer le planning de chaque secrétaire pour vérifier laquelle est présente
- tu récupères son numéro et l'appelle enfin
- tu récupères l'agenda du docteur pour vérifier quelle date est disponible
- tu vérifies sur le bureau de la secrétaire si elle a un stylo
- tu vérifies que le stylo n'est pas vide
- tu mets le stylo dans la main de la secrétaire. attention, il te faut savoir si elle est droitière ou gauchère
- ouf tu as ton rdv
bien sur chacune de ses actions pourrait encore être découpée pour que l'exemple soit encore plus absurde
Alors qu'un vrai service ressemblerait à
- appeler le docteur
-> dispatch de l'appel à la secrétaire disponible
-> proposer dates disponibles
- choisir une date de rendez-vous
done
Et si demain le docteur décide de changer quelque chose, ajouter/supprimer une secrétaire, que les agendas de rendez-vous sont maintenant électroniques, les bureaux, ... d'un côté tu dois reprendre ton code pour coller, de l'autre osef et seuls ceux qui implémentent ce flow auront à changer quelque chosele 28/01/2016 à 10:17 -
Luc HermitteExpert éminent séniorDéméter s'inscrit dans la continuité de l'abstraction -- c'est l'abstraction qui cache les détails et stabilise les interfaces, l'encapsulation assure les invariants.
Quand ton agent expose 150 propriétés, et qu'il faut passer par ces dernières pour le manipuler, l'abstraction a fuit.
Et quand tu parles du secrétaire, tu es encore en train de penser détail au lieux de t’adresser à une façade qui va redispatcher ton besoin à l'agent qui sait le résoudre. Pire, tu ne pourras pas t'adresser de manière identique à un cabinet (ce mot est important) qui a un(e) secrétaire, ou pas, ou carrément qui fait passer par une interface web pour prendre des RDV. Je n'ai que faire de mon interlocuteur pour prendre mon RDV tant qu'il est pris.
Démeter est certes vite excessive, mais elle pousse dans une bonne direction quand on cherche à la suivre.
PS: je ne vois pas le rapport avec la sémantique de déplacement, ni même avec a*X + Y, expressions templates ou pas. Je ne demande certainement pas à ma matrice une référence interne pour aller taper dedans.le 26/01/2016 à 18:18 -
koala01Expert éminent séniorOhhh, il y a tant à dire sur ton article, je vais encore une fois exploser la base de données
Première ineptie :
Disons-le d’emblée, Déméter est un leurre ! Une curiosité tout au plus.
Ce n'est pas un leurre, c'est une réalité : les services fournis par une abstraction donnée doivent s'assurer que la donnée qui réagit à l'ordre qu'on lui donne sera dans un état cohérent après avoir réagi à cet ordre si elle était dans un état cohérent avant que l'ordre ne soit donnée.
Bien sur, si la donnée n'était déjà pas dans un état cohérent avant que l'ordre ne soit donnée, on ne peut plus rien, mais c'est sans doute justement que Déméter n'a pas été respecté "ailleurs" et qu'on a laissé à l'utilisateur de notre type de donnée une "chance de faire une connerie"; chaque que l'utilisateur se sera empressé d'attraper en vertu de la loi de finagle
deuxième ineptie :Avec Déméter, l’ensemble des services offerts par les composantes de la classe Agent doivent être répliqués au niveau de celle-ci. Cela peut faire un paquet de nouvelles méthodes !
(emphasis is mine)
Ne devront être "répliqués" au niveau d'une classe utilisatrice que les services fournis par ses composants qui on du sens au niveau de la classe utilisatrice.
Un exemple : une classe voiture qui utilise une classe réservoir. on se fout pas mal, lorsqu'on manipule une voiture de connaitre la capacité maximale du réservoir : les services que la classe voiture expose afin de pouvoir interagir avec le réservoir sont là pour s'assurer que, quoi qu'il arrive, nous ne pourrons pas dépasser la capacité maximale du réservoir.
Le réservoir dispose d'une fonction maxCapacity() renvoyant la capacité maximale du réservoirC'est tout à fait normal : c'est l'un des services que l'on est en droit d'attendre de la part de ce type de donnée. Et c'est aussi un des services auquel les différentes fonctions membres de la classe voiture qui ont pour but de manipuler le réservoir risque de faire appel très régulièrement.
Mais, au niveau de la voiture, l'idée est que nous n'avons absolument pas besoin que cette information soit "dévoilée" : les ordres que nous pourrons donner à notre voiture devront "simplement" veiller à renvoyer la quantité exacte de carburant qu'il nous a été possible de rajouter ou d'utiliser dans les limites admises par la capacité maximale
troisième ineptie :Il faut bien comprendre que les composantes de la classe Agent peuvent avoir leurs propres composantes, et ainsi de suite. C’est un des avantages des types bien conçus, que de pouvoir être réutilisés facilement pour construire d’autres types. Agent::Savoir, par exemple, pourrait donner accès à une base de Faits, ainsi qu’à une liste d’Agents connus . Le principe de Déméter étant par essence récursif, ces sous-composantes vont à leur tour faire grossir le nombre de méthodes à ajouter dans Agent.
Or, comme il n'en est rien, tu as tout à fait tord sur ce point : si une classe A utilise une classe B et une classe C ; que la classe B utilise une classe D et une classe E et que la classe C utilise une classe F et une classe G sous une forme (chaque trait correspond à une relation "utilise" ou est utilisé par") proche de Code : 1
2
3
4
5
6A / \ B C / \ / \ D E F G
De même, les services proposés par A pourrons (ou non !!!) ** peut-être ** faire appel à tous les services proposés par B ou par C, mais il n'en restera pas moins que l'on ne retrouvera pas forcément l'ensemble des services de B (ou de C) dans l'interface de A
Quatrième ineptie :Enfin, il faut comprendre que les types de ces composantes, conçus pour représenter des concepts importants du S.M.A., ont des chances d’être réutilisés en divers autres endroits. Par exemple, la classe Comportement pourrait également servir de composante à un type Machine. La loi de Déméter implique d’effectuer le même travail d’ajout de méthodes pour la classe Machine que celui réalisé pour Agent, occasionnant une certaine redondance quand bien même ces deux classes partagent des composantes de même type.
ON SE FOUT PAS MAL DES DONNEES QUI PERMETTENT A UNE CLASSE DE FOURNIR LES SERVICES QU'ELLE PROPOSE. Tout ce que l'on veut, c'est qu'elle propose un certain nombre de services, un certain nombre de comportements qui soient- cohérents par rapport au concept que l'on tente de modéliser
- en mesure de garantir la cohérence du concept à l'exécution.
Il se peut que deux concepts "complexes" utilisent en interne le même concept "plus simple", mais :
- il se peut que nos deux concepts "complexes" utilisent le concept plus simple d'une manière totalement différente et, si ce n'est pas le cas,
- la création d'une interface exposant les comportements communs à nos deux concepts "complexes" évitera tout risque de redondance
Maintenant, si on s’intéresse au code client, il est vrai qu’avant Déméter, le code dépend de la classe Agent ainsi que de ses composantes. Après Déméter, le code client ne dépend plus que de la classe Agent seule, mais étant donné que celle-ci a vu son interface publique augmenter jusqu’à inclure l’ensemble des fonctionnalités disponibles auparavant via ses composantes, on a une autre forme de dépendance.
En fait, avec une gestion dynamique des composantes de la classe Agent [penser unique_ptr], il est possible avant Déméter de ne faire voir au code client que les composantes effectivement manipulées, voire aucune le cas échéant. Avec Déméter, le code client voit systématiquement toutes les fonctionnalités possibles, même s’il n’en utilise aucune…
La création des nouvelles méthodes peut entraîner de sérieuses difficultés de nommage, particulièrement en cas de structure profonde.le nom d'une fonction doit exprimer clairement l'objectif poursuivi par la fonction.
Et rien n'empêche d'avoir deux fonctions portant le même nom (car destinées à obtenir un résultat identique) mais utilisant des paramètres différents... Cela s'appelle : la surcharge de fonctionsSi, du point de vue interne, la classe Agent conserve sa structure de composantes, du point de vue externe, le client ne voit que l’interface publique, et donc pour lui, Agent est devenue une classe monolithique, d’un abord difficile par conséquent.
Mais, encore une fois, ton hypothèse est fausse: tout nous incite à faire en sorte que la classe utilisatrice n'expose QUE L'ENSEMBLE MINIMAL INDISPENSABLE DES SERVICES QUE L'ON EST DECEMMENT EN DROIT D'ATTENDRE DE LA PART DU CONCEPT MODELISE. En deux mots : respecte également les principes SOLID (dont l'ISP en priorité), et tu te rendras compte que ton hypothèse de travail vole littéralement en éclatsFinalement, et c’est le constat le plus sévère, Déméter tend à maximiser l’interface publique des classes, ce qui accroît directement la complexité globale du code…
Mais c'est faire preuve d'une bien mauvaise approche conceptuelle que de se limiter à l'usage de cette seule loi. Les principes SOLID sont d'égale importance par rapport à cette loi et vouloir appliquer l'un sans l'autre n'a absolument aucun sens!
Revois ta conception, applique les principes SOLID en même temps que la loi de Déméter, et tu verras que toutes tes thèses ne tiennent absolument plus!
Bon, je vais m'arrêter là, mais je ne dirais qu'une seule chose : ton approche est loin d'être assez complète et "conceptuellement cohérente" que pour que l'on puisse prendre tes conclusions au sérieuxle 26/01/2016 à 20:09 -
ternelExpert éminent séniorPar contre, tu perds la possibilité d'avoir un agent qui choisit son comportement parmi plusieurs, en fonction de ce qu'il estime être le besoin.
Tu perds aussi la possibilité d'avoir un Agent qui implémente son propre comportement.le 28/01/2016 à 13:45 -
Emmanuel DelogetExpert confirméLà, tes collègues risquent de dire à ton chef que tu les perturbe un peu pendant leur travail. Mais c'est à tenter...
Voui. Mais dison plutôt : la clarté et l'efficacité.
Pas d'accord. La clarté dépends uniquement du bon choix des noms de méthode. Une méthode correctement nommé est claire. Un identifiant qui fait entre 5 et 25 caractères est largement lisible par tout programmeur qui se respecte.
Ensuite : pas d'accord non plus. Non, on ne va pas se trouver à faire 40 inline - ça n'aurait pas de sens. Si on fait 40 inline dans une classe, alors il y a un vrai problème de non respect du SRP. Donc on ne va pas pourrir les docs, qui ne sont pas potentielles d'ailleurs, mais contractuellement exigée par la plupart des clients sensés.
Donc au final : pas d'accord avec la conclusion. Ma conclusion est "plus de clarté, pas plus de perf -> on garde".
Je vois pas trop l'impact des rrefs la dedans.
C'est une loi de style. La taxonomie des loi/principes/patterns/... n'impose pas qu'une loi de style doit être nécessairement suivie. Chacun voit midi à sa porte.le 16/10/2009 à 4:56 -
3DArchiRédacteurLe principe de demeter n'indique pas qu'il faille mettre tout à plat pour éviter de le violer. Mettre tout à plat en ajoutant 1000&1 méthodes pour ne pas le violer, c'est tout simplement mal corriger un problème de conception. En général, si tu commences à trop enfreindre cette loi ou que ta classe a trop de méthodes pour éviter de l'enfreindre, c'est probablement qu'il y a un problème de conception et que le niveau d'abstraction n'est pas le bon.le 16/10/2009 à 12:39
-
Emmanuel DelogetExpert confirméLa règle de "pas de référence circulaire au niveau des classes" a autant de sens qu'au niveau du package. En fait, c'est un corolaire du principe d'inversion de dépendance - si une référence circulaire existe, alors l'inversion de dépendance n'est pas possible - et une conséquence directe du principe de responsabilité - si la classe A dépends de B, alors B est une responsabilité de A. Si B dépends aussi de A, alors A est une responsabilité de B. Si les deux conditions sont réunies, ni B ni A ne peuvent avoir d'autre responsabilité selon le principe de responsabilité unique, et logiquement, le code ne fait rien. Pas d'une utilité fameuse tout ça...
Ce n'est pas tout. Les références circulaires sont aussi préjudiciables à l'application du principe ouvert/fermé - dans le sens ou il devient difficile voire impossible d'étendre du code sans modifier le code existant dès lors que ce qu'on souhaite étendre s'auto-référence.
Trois des principaux principes de conception objet qui sont malmenés sur les 5 les plus utiles, je trouve que c'est un bon chiffre qui nécessite qu'on s'attarde un peu à ce point particulierGlobablement d'accord mais la limite est parfois ambigue. Si tu refuses setPosition() mais que tu admets moveTo() (le deux faisant exactement la meme chose), ca ne me gene vraisemblablement pas.le 20/10/2009 à 14:35 -
koala01Expert éminent séniorEn quoi ai-je été discourtois dans mon message
j'ai peut être été brutal dans le choix de mes termes, mais j'ai visiblement atteint mon objectif : te bousculer assez pour te forcer à réagir.
En outre, je ne vois nullement où j'ai pu me montrer "présomptueux". je n'ai jamais essayé de me mettre en avant de manière inconsidérée, et je n'ai donné dans cet intervention aucun avis "d'autorité": j'ai à chaque fois essayé de justifier ma réaction .
Et si tu trouves présomptueux de ma part de dire "je vais encore une fois exploser la base de données", saches qu'il 'est déjà arrivé plus souvent qu'à mon tour de vouloir poster une réponse et d'obtenir un message du genre "désolé, mais la taille des message est limitée à XXXXX (65 535, si mes souvenirs sont bons) caractères et votre message en fait (heu... entre "quelques uns" et "beaucoup" de plus). Cette phrase n'était donc rien de plus qu'un clin d'oeil au fait que je risquais -- encore une fois -- de vouloir écrire un roman que se serveur refuserait d'enregistrer pour cause de "nombre de caractères excessifs". le 27/01/2016 à 2:11