Je voulais partager avec vous la plus belle ligne de code que je connaisse:
if (condition) return
Voilà, simple, efficace.
On peut exprimer la même chose un peu différemment, en profitant de ruby par exemple:
return if condition
ou encore:
return unless autreCondition
C'est beau n'est ce pas?
Bien sûr cela ne fait pas grand chose, il faut même considérer que cela ne fait rien, ou plutôt, cela arrête ou empêche de faire, avec ce return
indispensable.
D'ailleurs vous pourrez remarquer qu'il vaut mieux mettre cette ligne avant d'autres, sinon elle n'a absolument aucune utilité, ce qui la rendrait bien peu intéressante.
if (objetDeTravail == null) return;
objetDeTravail.callOtherMethod();
objetDeTravail.callYetAnotherMethod();
Ah oui, là vous pouvez remarquer un usage très fréquent de ce type de clause, en java => s'assurer que l'on n'appelle pas une méthode sur un objet null
. Ce qui enclenche une malédiction malfaisante sur la malheureuse machine où s'exécute ce malheureux code.
On pourrait exprimer la même chose sans utiliser notre belle ligne de code avec son return
si bien placé, cela donne alors quelque chose comme ça:
if (objetDeTravail != null) {
objetDeTravail.callOtherMethod();
objetDeTravail.callYetAnotherMethod();
}
C'est fou non? On voit de mieux en mieux ce qu'apporte notre magnifique ligne: moins d'accolades, de begin/end, d'indentations! :-)
Mais, certaines recommandations de style enjoignent, ou enjoignaient, à ce que le code qui n'a qu'un point d'entrée n'ait aussi qu'un seul point de sortie. Afin de proposer une lecture la plus linéaire possible.
Ce qui pouvait donner ce genre de construction:
def methodeLineaire:
result = ""
if condition:
result = "autre résultat"
return result
Alors que franchement:
def methodePlusBelle:
if not condition: return ""
return "autre résultat"
Moins de variable, moins de potentiels else
, on teste et on sort vite, une fois ou plusieurs s'il le faut. Car oui, on peut multiplier cette belle ligne, on rajoute des conditions de sortie autant de fois que nécessaire!
Un des effets notables de ce style de code, avec des clauses qui bloquent l'exécution le plus tôt possible, c'est que l'on découvre des points pivot dans notre code, que l'on peut transformer en méthode protégées par ces "gardes".
Parois des centaines ou des milliers de lignes s'accumulent et se regroupent, mystérieusement, dans une seule méthode. Des enchaînements de blocs de code, avec des chemins d'exécution un peu compliqués, des duplications ici et là, des conditions très complexes, des variables utilisées du début à la fin mais pour des usages différents, des boucles contenant des boucles contenant des boucles, une horreur à relire et à maintenir. Promis, je n'ai jamais jamais fait ce genre de code personnellement⸮
Pour sortir de cet enfer on peut justement se concentrer sur ces pivots, ces chemins d'exécution qui sont "protégés" par des conditions peut-être un peu mal organisées. Souvent le plus simple est d'étudier les blocs de code dans leur profondeur.
Car oui, une méthode constituée de milliers de lignes de code est souvent aussi très profonde: elle contient des boucles contenant des conditions avec d'autres boucles et conditions imbriquées, sur tellement de niveaux que si le code est bien indenté, et bien on a besoin de faire défiler la vue horizontalement! /o\
Ces blocs de code tout en profondeur sont souvent de bons candidats pour être "promus" dans leur propre méthode. On les enferme dans leur propre boite, et avec un nommage décent on rend tout ce plat de spaghettis un peu plus digeste!
Je crois que, de plus en plus, les programmeurs cherchent à grandement limiter la profondeur de leur programme, pour le rendre plus lisible et plus maintenable. Et je constate que ça marche bien. On obtient du code aéré, bien découpé, élégant!
Il est si beau ce code, je voudrais le garder pour moi et ne jamais le pousser sur le dépôt central… dans la grande jungle de l'équipe des développeurs, où il risque tellement de tomber sur un moi d'il y a vingt ans!
# Inspiration
Posté par Enzo Bricolo 🛠⚙🛠 . Évalué à 4.
Kevlin Henney ?
[^] # Re: Inspiration
Posté par Guillaume Denry (site web personnel) . Évalué à 2.
Excellente vidéo, excellent orateur, merci
[^] # Re: Inspiration
Posté par Enzo Bricolo 🛠⚙🛠 . Évalué à 3.
Les vidéos de Kevlin sont souvent pertinentes (e.g. "Old is the new new"), je les partage à la cantonade.
[^] # Re: Inspiration
Posté par echarp (site web personnel, Mastodon) . Évalué à 3.
Du tout, je ne connaissais pas, mais je vais regarder ça de plus près.
# return early pattern
Posté par woffer 🐧 . Évalué à 10. Dernière modification le 14 octobre 2023 à 09:21.
Connu aussi comme return early pattern.
Il permet de diminuer la complexité, rend le code beaucoup plus lisible et évite le code Hadouken :
[^] # Re: return early pattern
Posté par echarp (site web personnel, Mastodon) . Évalué à 3.
Top cette image!
Moi j'appelle aussi ça des escaliers vers l'enfer :)
[^] # Re: return early pattern
Posté par fearan . Évalué à 4.
et encore là on a un truc simple avec juste de if if if if if puis else else else…
on a un bel double escalier
J'ai un collègue qui me fait if if else if else if if else…, le tout en cœur de boucle et sur un écran de haut…
Il ne faut pas décorner les boeufs avant d'avoir semé le vent
[^] # Re: return early pattern
Posté par SpaceFox (site web personnel, Mastodon) . Évalué à 9. Dernière modification le 15 octobre 2023 à 02:07.
Dans tous les cas, si le code a autant de niveaux d’indentation – ou des milliers de lignes dans une seule méthode comme le mentionne le journal –, c’est un code qui a des problèmes bien plus graves que juste quelque chose qui se résous à coup de return early pattern. C’est un code qui a une complexité cyclomatique délirante, et c’est ce point qu’il faut corriger en premier. D’autant plus que si ce genre de méthode arrive à exister au sein du code, c’est aussi presque toujours signe qu’il n’y a aucun test digne de ce nom.
La connaissance libre : https://zestedesavoir.com
[^] # Re: return early pattern
Posté par David Demelier (site web personnel) . Évalué à 7.
Un de mes collègues m'a fait une merge request comme ça, avec 9 niveaux d'indentation en mettant toute la logique dans un nouvel indent.
Il ne comprend pas non plus pourquoi je demande à ce qu'on dépasse pas environ 100 colonnes dans le code. Bah… tout simplement parce que le diff la merge request ne passait pas verticalement avec toute cette indentation.
Le grand problème quand ce genre d'individu a toujours travaillé tout seul sans jamais regarder le code de quelqu'un d'autre ou d'un projet opensource.
git is great because linus did it, mercurial is better because he didn't
[^] # Re: return early pattern
Posté par groumly . Évalué à 3.
Autant les pyramids of doom sont un VRAI probleme, et je matraque constamment a mes équipes que
guard else
c'est pas fait pour le chien (oui, on fait du swift, et guard a en cadeau bonux une garantie du compilateur que le block return ou fatalError()), que la lisibilité et réduire le contexte a garder en tete en relecture est plus important qu'une règle arbitraire "un seul return par fonction", autant l'excuse du "le diff est pas lisible" est assez bidon.Utilise un outil different si ton outil est pas capable de faire des trucs de base. J'ai eu le meme débat (assez houleux) a propos de nos règles PMD qui interdit les "space terminated strings", l'excuse donnée étant que le mec qui a mit au point les règles et qui faisait pas mal de review utilisait un outil des bois qui était pas foutu de filtrer les whitespace des diffs.
Bon, apres, si tu fais surtout du python, effectivement, c'est une autre histoire.
Linuxfr, le portail francais du logiciel libre et du neo nazisme.
[^] # Re: return early pattern
Posté par David Demelier (site web personnel) . Évalué à 9.
L'outil en question est une merge-request sur GitLab avec un écran 27". Je pense que quand le diff ne passe pas sur l'écran splité en deux c'est qu'il y a un vrai problème.
git is great because linus did it, mercurial is better because he didn't
[^] # Re: return early pattern
Posté par Julien Jorge (site web personnel) . Évalué à 6.
J'ai aussi rencontré le problème, des lignes interminables et des diffs illisibles même en plein écran en enlevant toutes les fioritures autour. J'ai aussi l'impression que les devs qui codent ainsi passent proportionnellement trop peu de temps à lire le code des autres, et la réaction est toujours là même : change d'outil, achète un écran plus grand, et autres variantes du « chez moi ça marche ». Toujours plus de dépendances et de ressources, il n'y a personne pour pousser vers un environnement de dev plus léger :(
# Optional<T>
Posté par Media ex Machina (site web personnel, Mastodon) . Évalué à 3. Dernière modification le 14 octobre 2023 à 11:53.
Sinon, en java tu as la classe Optional, qui va gérer les cas des null, sans rajouter d’if…
[^] # Re: Optional<T>
Posté par ff9097 . Évalué à 5. Dernière modification le 14 octobre 2023 à 13:39.
ou juste utiliser Kotlin
monObj?.call()
[^] # Re: Optional<T>
Posté par barmic 🦦 . Évalué à 4.
Je n'ai jamais compris pourquoi ils ont implémenté Optional aussi tôt… Ils avaient déjà l'idée de ce qui allait se faire et aujourd'hui je trouve que c'est une classe qui utilise mal le langage. À mon avis Optional devrait être une interface sealed implémentée par un record disons Present et une classe (voir un singleton) Absent. Il ne devrait pas y avoir de méthode get() et l'interface ne devrait porter que le méthodes qui en font une monade. Tu aurais une classe dont aucune des méthodes ne peut lever de NPE et au lieu d'avoir tous les analyseurs statiques qui doivent tenter de vérifier si tu utilise get() dans un endroit sûr ce serait vérifié dans le langage par le typage (et pour le quel l'inlining peut rendre ton code efficace). Et tu n'aurait plus besoin de la méthode get() grâce au pattern matching (qui n'en est pas vraiment un en java). On va garder encore 20 ans une classe qui aura finalement rapidement était hors de ce que Java peut produire.
https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll
[^] # sealed ?
Posté par GwenLg . Évalué à 1.
Ah du coup question, c'est quoi le sens/l'idée du terme sealed ?
Je l'ai déjà vu passé dans du code, mais je n'ai pas compris le sens, l'idée que ça exprimait.
Merci
[^] # Re: sealed ?
Posté par barmic 🦦 . Évalué à 6.
Sealed c'est un moyen de contrôler l'héritage d'une classe ou d'une interface.
Jusqu'à l'arrivée de
sealed
on avait le mot cleffinal
qui indiquait qu'une classe ne pouvait avoir de fille.sealed
permet d'indiquer la liste exhaustive des filles direct d'une classe. Dans l'exemple au dessus,Optional<T>
n'a que 2 soustypePresent
etAbsent
.Ça peut être assez déroutant parce que ça contreviens au principe de substitution de Liskov, mais c'est pratique pour certaines choses. Ça ne doit pas être utilisé pour tout, mais pour des cas où on sait qu'on a un arbre de types limité qui ne devrait pas évoluer sans que le code qui l'utilise le prenne en compte.
Imaginons (je tire l'exemple de mon chapeau c'est peut être pas un bon exemple), tu veut gérer une machine à états finis, mais ça t'arrangerait que l'état soit plus qu'une simple valeur (donc pas un enum) par exemple pour aider au debug. Tu peut alors créer une classe ou une interface et dont tu sais que toutes les filles direct représente un état de ta machine.
L'intérêt c'est de pouvoir utiliser ça avec le pattern matching et les switch expressions, puisque tu va pouvoir manipuler ton état tout en garantissant l’exhaustivité des cas. Si tu ajoute un nouvel état c'est un nouveau sous-type et tu devra l'implémenter dans tout les endroits où tu a eu besoin de traiter tes états.
Une autre manière de voir c'est de s'en servir comme un type somme du pauvre. Tu peut créer un type T qui est soit A, soit B, soit C,… et écrire des fonctions qui sont polymorphiques sur leur paramètre (tu prend en paramètre un type T et en fonction du sous-type tu réagi différemment). Du pauvre parce que les sous-type ne peuvent pas être arbitraires comme tu le ferait en haskel.
Je suis un peu loquace ?
https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll
[^] # Re: sealed ?
Posté par GwenLg . Évalué à 1.
Je ne dirais pas que l'explication est limpide, mais elle m'a quand même aider à mieux comprendre le cas que j'avais rencontré.
Donc merci 👍
Note: le cas que j'avais rencontré est dans la lib Rust CityHasher :
https://docs.rs/cityhasher/latest/src/cityhasher/lib.rs.html#434
[^] # Re: sealed ?
Posté par Thomas Douillard . Évalué à 4.
Pourquoi ça contreviendrait au principe de substitution ? J’ai pas compris. Suffit toujours que les classes filles respectent les contrats de la classe mère non, c’est indépendant ?
[^] # Re: sealed ?
Posté par barmic 🦦 . Évalué à 3.
La classe mère qui liste ses classes filles d'une part et parce que c'est globalement fait pour écrire :
Ce qui blesse le cœur de tout ceux qui expliqueraient (non sans raison) que le polymorphisme c'est bien et qu'il faudrait une méthode décrite dans A et implémentée dans B et C. Ici le fait qu'une méthode hors de A, B ou C, cherche à déterminer le type sous-jacent de la référence a est la violation la plus classique de Liskov.
Présenté autrement : en respectant Liskov si tu crée un sous-type du type T tu devrais pouvoir remplacer n'importe quelle référence de T par une instance de ton sous-type sans changement du code qui utilise la référence. Ce n'est évidement pas le cas ici et c'est même un effet recherché.
C'est pour ça qu'il faut manier avec précaution dans un langage comme java qui utilise surtout le polymorphisme de classe et qui commence tout juste à utiliser ce genre de mécanismes (tout le monde appel ça du pattern matching, mais ça n'en est pas en java).
https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll
[^] # Re: sealed ?
Posté par Thomas Douillard . Évalué à 4. Dernière modification le 17 octobre 2023 à 17:06.
On a pas la même définition du principe de substitution de Liskov : https://blog.async-agency.com/arretez-de-developper-nimporte-comment-principe-ouvert-ferme-2/
Dans ton exemple la méthode foo doit pouvoir prendre en paramètre n’importe quel sous-type de A en respectant le contrat (sur A) de la méthode formatted, donc si formatted est implémentée correctement à mon avis on est bon pour le principe de substitution.
Il ne vaut pas forcément dans l’implémentation de la fonction mais dans les contrats définis sur les méthodes, je pense.
Mais tu veux ptete parler du principe d’encapsulation ?
[^] # Re: sealed ?
Posté par barmic 🦦 . Évalué à 5.
Si tu crée un nouveau sous type de A, ta fonction ne compile plus.
Le fais que du code utilisateur soit conscient des sous type de ses paramètres (ou de ce qu'il reçoit d'une fonction) empêche la substitution.
On parle de typage et pas de l'état interne des objets donc je dirais que non.
https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll
[^] # Re: sealed ?
Posté par Thomas Douillard . Évalué à 4. Dernière modification le 17 octobre 2023 à 19:17.
C’est pas plutôt le Principe ouvert fermé (O de SOLID), ça ? Si c’était impliqué par le principe de Liskov il n’y aurait pas besoin de l’avoir en principe en plus. Et je ne vois pas vraiment que le principe de Liskov implique que ton code doive compiler si tu rajoutes un sous type. Le seul truc du principe c’est qu’un sous type ne doit pas casser les invariants comportementaux d’un super type.
Cette implication n’est en tout cas pas du tout mentionné dans :en:Liskov substitution principle.
Notamment parce que par principe le fait que ce soit "Sealed", justement, empêche l’extension. Le sous typage est toujours possible en sous-typant les cas connus ?
[^] # Re: sealed ?
Posté par barmic 🦦 . Évalué à 3.
Tu montre bien qu'il y a des recoupements entre les concepts que l'on nomme 😊
Non effectivement il ne s'intéresse pas ou peu à l'utilisation, mais je pense qu'il s'agit d'un évitement du principe de Liskov, car l'objectif c'est de retirer des traits du parents et de les implémenter dans le code utilisateur.
Le fait que le code ne compile pas est extrêmement important et il faut vraiment y faire attention. Le type A peut techniquement être dans une bibliothèque chargée dynamiquement par exemple. L'utilisation et l'exposition de ce genre de type est plus risqué que du polymorphisme car l'arbre de type va faire partie du contrat (et pas uniquement le type parent).
En java en tout cas oui. À noter qu'il n'est pas possible de tricher par contre. Sealed est vérifié à la compilation et à l'exécution. La jvm t'interdira de charger une nouvelle sous classe ni vu ni connu en jouant avec le bytecode.
Je n'ai aucun problème à utiliser sealed, il y en a déjà un certains nombre dans mon code de prod et je suis impatient de pouvoir enfin utiliser les switch expression avec. Mais je comprends les gens qui sont un peu orthodoxes de la POO qui voient des problèmes avec cette forme de polymorphisme (il y en a aussi dans le polymorphisme de classe mais ils ne sont pas nouveaux pour eux).
Désolé pour mon commentaire plus bas j'avais oublié de le soumettre
https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll
[^] # Re: sealed ?
Posté par Thomas Douillard . Évalué à 4.
Pas tellement d’accord, j’ai l’impression que c’est plutôt une utilisation du principe de liskov pour implémenter un pseudo type somme en java.
Le sous-typage est utilisé transférer une propriété de la classe mère : "une de mes instance est instance est instance d’exactement un de ces types", à ses classes filles. Le respect du principe de substitution de Liskov implique que ce qui doit être vrai pour toute instance de la classe mère soit également vrai de toute instance de ses sous-types. Propriété qui est vérifié par le compilo et la jvm, ce qui ne sera pas forcément le cas dans le cas de Java pour d’autres types d’invariants ou de précondition qu’on pourrait attacher aux méthodes.
Après en lisant Wikipédia en anglais il peut effectivement y avoir un hiatus avec le terme ''substitution'', il semblerait que Liskov elle même plutôt récemment évite de l’utiliser et qu’une terminologie plus moderne soit :en:behavioral subtyping, plus précis et qui met l’accent sur les propriétés associées, et qui prend en compte les classes abstraites, on peut pas instancier ces classes et donc substituer des objets strictement de ce type … Elles voulaient définir ce qu’est un sous typage correct, en fait.
[^] # Re: sealed ?
Posté par barmic 🦦 . Évalué à 2. Dernière modification le 18 octobre 2023 à 00:02.
Comment l'absence de partage de trait pourrait être une utilisation du principe de Liskov ?
Si j'écris
Et si on applique le principe de Liskov, il n'y a pas de propriétés prouvable sur A a appliquer à B ou C. Le behavioural subtyping de la même façon parle d'un comportement défini dans le parent qui doit être respecté dans les enfants.
Tout le principe du
sealed
/switch
c'est de ne pas faire porter le comportement sur le type. Je suis d'accord pour dire que ce n'est pas un viol du behavioral subtyping ou du principe de Liskov, mais c'est fait en se plaçant hors de leur champ : on retire la propriété prouvable sur le type parent.https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll
[^] # Re: sealed ?
Posté par Thomas Douillard . Évalué à 3.
Il y en a au moins une : la propriété portée par le sealed lui même, dont héritent ses enfants. C'est un "invariant" au sens du sous-typage comportemental.
[^] # Re: sealed ?
Posté par barmic 🦦 . Évalué à 2.
Euh je vois pas. Quelle est la propriété que décrite sealed que tu peux appliquer aux filles ?
https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll
[^] # Re: sealed ?
Posté par Thomas Douillard . Évalué à 3.
Le mot clé sealed dit "toutes mes instances doivent être soit des B soit des C" (et pas les deux je pense)
Les B et les C héritent de l'interface A. Donc de la propriété. Et donc tout B étant un A est interdit d'être un C.
Vu comme ça sealed est juste une manière de définir un invariant de classe dans le langage.
[^] # Re: sealed ?
Posté par Thomas Douillard . Évalué à 3.
Ah mais non l’exclusivité est garantie par "class" mais pas par "interface" en java /o.
Dans ce cas il n’y a pas grand chose à hériter effectivement. Mais c’est pas forcément intéressant pour autant comme construction, c’est comme hériter "d’object" en java, qui est trivial. Mais ton exemple n’a pas l’air de compiler donc je suis pas certain de savoir ce que tu voulais dire exactement.
[^] # Re: sealed ?
Posté par barmic 🦦 . Évalué à 3.
J'avais écris le code rapidement sur téléphone, la version vérifiée c'est :
Mais je pense avoir compris ce que tu veux dire et je me suis clairement mélangé les pinceaux entre propriété de classe et d'instances.
Sans parler de Liskov ou de subtyping behavior, à mon avis il faut avoir conscience de ce que ça contraint sur le code et qu'il faut choisir une balance entre implémenter des comportements dans les
class
et faire porter le comportement dans des méthodes extérieures aux classes.https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll
[^] # Re: sealed ?
Posté par Thomas Douillard . Évalué à 4. Dernière modification le 17 octobre 2023 à 18:18.
J’ai vu que Wikipédia avait jusqu’à aujourd’hui un paragraphe qui allait dans ton sens dans Principe de substitution de Liskov … Ce qui a sûrement aidé à propager cette idée … qui n’était pas vraiment sourcée sérieusement.
Il n’y avait même pas de sous-typage dans l’exemple, donc a fortiori pas de contrats comportementaux, donc rien qui ne puisse violer la définition formelle :
[^] # Re: sealed ?
Posté par Thomas Douillard . Évalué à 4.
StackOverflow confirme : https://stackoverflow.com/questions/62602914/does-instanceof-operator-violate-liskov-substitution-principle
[^] # Re: sealed ?
Posté par barmic 🦦 . Évalué à 3.
Comme je le disais plus haut ça viole un principe de substitution (sans parler de Liskov) car le code appelant est conscient de l'ensemble du typage et donc que tu ne peu plus ajouter un sous type sans récrire le code qui utilise ton objet.
Je comprends que Liskov ne s'intéresse qu'à la modélisation et pas à comment ils sont utilisés, mais ici il s'agit de retirer les traits du type pour le mettre dans le code appelant. On peut effectivement dire que ça n'est pas un viol du principe de Liskov, mais c'est une manière de s'en passer. Dans des cas extrêmes, mais qui ne sont pas difficiles à trouver, tu n'a aucun trait dans le type parent.
https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll
[^] # Re: sealed ?
Posté par groumly . Évalué à 3.
Ça casse pas plus le principe de liskov que final (interface a, public final class b implements a).
Liskov dit que les classes filles doivent conserver exactement le même contrat que la classe mère. Pas que la classe mère peut être étendu à l’infini.
Linuxfr, le portail francais du logiciel libre et du neo nazisme.
[^] # Re: Optional<T>
Posté par SpaceFox (site web personnel, Mastodon) . Évalué à 3.
Parce qu’ils en avaient besoin pour gérer toute la programmation fonctionnelle à l’arrivée de Java 8, époque où les types scellés ou le pattern matching n’étaient encore qu’un rêve en Java.
La connaissance libre : https://zestedesavoir.com
[^] # Re: Optional<T>
Posté par barmic 🦦 . Évalué à 3.
Bof si c'est pour
findAny()
etfindFirst()
, ils auraient pu retourner des valeurnull
comme le fait l'API de collection. Ça me parait moins pire que de se coltiner du code des décennies alors qu'on avait déjà planifié au moment de son écriture qu'elle allait être désuète. Le plus drôle c'est que vavr utilise déjà ce genre de design avec java 7…Dans 2 ans on aura :
Ouai !…
https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll
[^] # Re: Optional<T>
Posté par SpaceFox (site web personnel, Mastodon) . Évalué à 2. Dernière modification le 15 octobre 2023 à 01:58.
Non, c’est plus vaste que ça, ça permet en particulier d’écrire des chaines d’appels complètes sans avoir à gérer des
null
en plein milieu et de couper la logique fonctionnelle avec des batteries deif
.D’ailleurs, la doc dit :
Note aussi que, dès aujourd’hui, tu peux aussi utiliser l’un des trop nombreux frameworks qui permettent de gérer les
null
avec des annotations@Nullable
/@NonNull
. C’est juste moins intégré au langage lui-même.La connaissance libre : https://zestedesavoir.com
[^] # Re: Optional<T>
Posté par barmic 🦦 . Évalué à 3.
Ça n'est pas plus vaste, c'est un choix de vouloir continuer à avoir une API fluent un peut plus loin que les Stream. Je ne crois pas que le prix en vaille la chandelle. Avoir une API fluent ce n'est pas de la programmation fonctionnelle, ça vient à mon avis d'un mélange : la programmation fonctionnelle ne connaît que des expressions et pas d'instruction et éventuellement ils ont des monades quand ils ont besoin.
La JSR305 est sympa même si elle mériterait d'avoir un peu plus d'amour (de la part de ces concepteurs et des utilisateurs). Elle a l'avantage d'être utilisable dans bien plus de cas.
https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll
[^] # Re: Optional<T>
Posté par Letho . Évalué à 1.
Je suis d'accord sur le fond - bien que je soie plutôt content de pouvoir chaîner des
.map
sur unOptional
depuis Java 8 :)Dans le même ordre d'idée, j'adorerais un équivalent standard en Java du
Result<T, E>
de Rust. Peu probable au vu de la sacro-sainte rétro-compatibilité.[^] # Re: Optional<T>
Posté par fearan . Évalué à 4.
même pas Optional autorise null…
Il ne faut pas décorner les boeufs avant d'avoir semé le vent
[^] # Re: Optional<T>
Posté par Nicolas Boulay (site web personnel) . Évalué à 4.
Le compilateur pourrait traiter un pointeur comme un Optional<*,NULL> et vérifier que le cas NULL est bien filtré.
"La première sécurité est la liberté"
[^] # Re: Optional<T>
Posté par groumly . Évalué à 4.
euh, ouais, pas vraiment.
Optional.get()
peut te peter unNoSuchElement
(unchecked en plus, la classe) et la difference entremyVar.isPresent()
etmyVar != null
est quand meme vachement subtile.Sans compter que ca devient vachement meta quand tu te rends compte que ton optional peut être null, et qu'en fonction du contexte, tu vas potentiellement devoir null check ton optional.
Linuxfr, le portail francais du logiciel libre et du neo nazisme.
# j'ai du mal à comprendre un truc ....
Posté par totof2000 . Évalué à 4.
Ne serait-il pas plus judicieux de traiter l'appel de méthode sur objet null via une exception ?
[^] # Re: j'ai du mal à comprendre un truc ....
Posté par echarp (site web personnel, Mastodon) . Évalué à 4.
Les exceptions rajoutent un chemin d'exécution, pas forcément simple à suivre malheureusement.
Et ça rajoute de la complexité au niveau de l'appelant, là où justement on essaye de simplifier en utilisant une méthode.
Pour les NPE le plus simple c'est l'opérateur
?.
qui permet de proprement gérer l'éventuelle nullité. C'est en train d'arriver petit à petit dans beaucoup de langages.Et puis d'avoir une garantie de présence comme dans rust, ça se tente.
[^] # Re: j'ai du mal à comprendre un truc ....
Posté par barmic 🦦 . Évalué à 5.
Ça n'est pas forcément un cas exceptionnel.
Tu peux vouloir écrire :
Mais sinon c'est une option à prendre en compte : ne rien faire et laisser l'exception arriver. Maintenant que les
NullPointerException
donnent (enfin !) des informations sur ce qui étaitnull
. Ça vaut le coup de laisser faire le langage naturellement.https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll
[^] # Re: j'ai du mal à comprendre un truc ....
Posté par totof2000 . Évalué à 4.
Je suis peut-être puriste, mais retourner une taille de 0 pour un champ alors que l'objet est de type null, ça me pose problème. C'est pour ça que je préfèrerais gérer ça via exception … Mais c'est peut-être parce que j'ai fait trop d'Erlang dont la philosophie est de ne pas faire de programmation défensive mais de laisser planter (et avoir un superviseur qui relance le processus).
[^] # Re: j'ai du mal à comprendre un truc ....
Posté par barmic 🦦 . Évalué à 4.
Null n'est pas un type en java.
Ça m'embête supposé très doctrinal. Ça dépend complètement de la sémantique que tu donne. Si je compte la taille d'un message, je peux vouloir considérer que sa taille est nulle ou valant 0 qu'il s'agisse d'une chaîne vide, d'un champ absent ou d'un champ ayant pour valeur null ou undefine si c'est représenté par du json par exemple. Multiplier les chemins d'exécutions pour ça n'a pas de sens et planter pour relancer le processus est un bug : une valeur nulle n'est pas synonyme d'erreur.
Penser que les choses ont un sens intrinsèque et que ton modèle est universel est à mon avis une erreur pas lié au langage ou à la stack utilisée.
Tu peux vouloir éliminer les valeurs null en les représentants dans ton typage et c'est très bien, tu n'a plus à avoir ce genre de garde dans tes fonctions mais c'est pas une absence de programmation défensive, elle est déplacée aux entrées de ton programme. Mais ça ne me paraît pas pertinent si le langage ne permet pas de l'exprimer. Java ne le permet pas et il me semble qu'erlang non plus toute références peuvent être
nil
il me semble.https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll
[^] # Re: j'ai du mal à comprendre un truc ....
Posté par echarp (site web personnel, Mastodon) . Évalué à 3.
A quel point la notion est utile ou même obligatoire en informatique?
Notamment au niveau base de données.
Moi je jongle un peu trop avec des tables comportant des colonnes optionnelles par exemple… donc null :(
[^] # Re: j'ai du mal à comprendre un truc ....
Posté par echarp (site web personnel, Mastodon) . Évalué à 4.
À quel point la notion de
null
…[^] # Re: j'ai du mal à comprendre un truc ....
Posté par barmic 🦦 . Évalué à 4.
À moins que ça m'échappe je ne connais pas de langages de programmation commun1 qui ne l'implémentent pas il me semble. Mais il y a 2 manières de faire soit c'est une valeur qui est valide dans tous les types nullables (donc toute références en java, tout pointeur en C/C++, etc) soit c'est un type.
Quand c'est un type généralement le langage permet de créer des types sommes ce qui permet de créer un type « entier ou null » et ça t'oblige à vérifier que c'est un entier avant de pouvoir utiliser ta variable. Je trouve ça plus élégant personnellement.
Je ne classe pas Malbolge comme commun, ni les langages simulant une machine de Turing ↩
https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll
[^] # Re: j'ai du mal à comprendre un truc ....
Posté par groumly . Évalué à 6.
Le probleme c'est que les memes causes ont généralement les memes consequences, laisser le process planter et prier tres fort qu'il se banane pas apres avoir été relancé, c'est tres #yolo quand meme. Si la cause du bug c'est une race, ou un évènement ponctuel spécifique qui est null quand il devrait pas l'être, c'est une chose, et ton process va probablement revenir a la normal (ou pas), potentiellement au prix d'avoir atomise les autres transactions qui étaient potentiellement en cours dans le meme process (donc potentiellement, pas nul du tout).
Mais si c'est un job qui process des données, ou profile utilisateur qui a un champ nul quand il devrait pas, tu peux relancer le processus autant de fois que tu veux, ca va pas mieux marcher apres le millionième relancement que la premiere fois.
Soit ton typage comprends la notion de null, et tu force le call site lui meme a répondre a la question existentielle "une collection qui n'existe pas peut elle avoir une taille?", soit il le fait pas. Dans ce cas, t'as 2 options: declarer qu'une collection qui n'existe pas a une taille de 0, ou decider que c'est une exception (et ca, ca va vraiment pas arranger la lisibilité du code, parce qu'une exception c'est juste 2 goto empilé l'un sur l'autre qui portent un gros imperméable avec un faux nez et une fausse moustache sur celui du haut).
Au sujet de la semantique, declarer qu'une collection nulle a une taille de 0 est loin d'être ouf. Si tu regardes la question sous l'angle "combien de patates sont dans ce sac a patates", effectivement, c'est mal définit quand le sac a patate n'existe pas.
Mais la question réellement posée a 99% de chances d'être "De combien de patates suis-je en possession?", et ca enleve l'ambiguïté: tu ne possedes aucune patate si tu ne possèdes pas de sac a patates, ou si le sac a patates est vide. Et c'est a priori renforcé par le fait que si t'as ce probleme en premier lieu, t'es en mode fonctionnel, pas objet (sinon t'aurais le probleme du null avant meme de poser la question), et donc la question est littéralement "combien de patates?" pas "quelle est la taille de ce sac a patates?".
Tu peux probablement trouver une variante de ce probleme qui ne se résoud pas en changeant l'énoncé de la question, mais dans ce cas ci, ca se résoud tres facilement.
Linuxfr, le portail francais du logiciel libre et du neo nazisme.
[^] # Re: j'ai du mal à comprendre un truc ....
Posté par BAud (site web personnel) . Évalué à 6.
oui :-)
et ça a même un nom, cela s'appelle la méthode Shadoks : « plus ça rate, plus on a de chances que ça marche » /o\
[^] # Re: j'ai du mal à comprendre un truc ....
Posté par barmic 🦦 . Évalué à 3.
En erlang les threads userlands sont appelés process (ouai je trouve pas que ce soit un super nommage) et je pense que ça de ça qu'il parlait donc ça ne devrait pas être aussi impactant.
Oui le nommage de ma fonction à l'arrache n'était pas génial et tu as mieux exprimé que moi ce que je voulais dire par le fait que c'est une question de sémantique.
https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll
# Shell
Posté par Benoît Sibaud (site web personnel) . Évalué à 6. Dernière modification le 14 octobre 2023 à 13:31.
(suivant les cas)
[^] # Re: Shell
Posté par barmic 🦦 . Évalué à 7.
J'aime beaucoup perl pour ça :
et je le reproduit en shell quand le cas se présente en écrivant ma propre méthode
die()
https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll
[^] # Re: Shell
Posté par gUI (Mastodon) . Évalué à 4.
Il y a une erreur, il faut écrire
En théorie, la théorie et la pratique c'est pareil. En pratique c'est pas vrai.
[^] # Re: Shell
Posté par Gil Cot ✔ (site web personnel, Mastodon) . Évalué à 2.
Bah, kif-kif non ?
perl ↔ shell
condition or exit
↔condition || exit 1
condition or die ""
↔condition || return
condition or die "message"
↔condition || { echo "message" >&2 ; return }
Je n’utilise une fonction de sortie dédiée que quand je veux faire des trucs chiadés (par exemple écrire dans un log en plus d’envoyer un message en couleur sur le terminal) et ou mutualisé (par exemple une série de tests pour fail early avec le même message, donc mode DRY)
“It is seldom that liberty of any kind is lost all at once.” ― David Hume
[^] # Re: Shell
Posté par barmic 🦦 . Évalué à 3.
Exactement sauf que je n'utilise jamais de conjonction de bloque donc pas de
{…} && {…} || {…}
(c'est verbeux et piégeux, par exemple il manque un ; dans ton dernier exemple d'après mon dash 0.5.12) et j'évite lesreturn
en shell particulièrement parce que maintenant bash ne les accepte plus que dans certaines conditions.https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll
[^] # Re: Shell
Posté par Gil Cot ✔ (site web personnel, Mastodon) . Évalué à 2.
Ah on est d’accord sur le côté piégeux :) (pour la verbosité il y a moins de caractères que d’autres formes)
Les
return
en fait c’est pour les fonctions utilitaires ; ça quitte la fonction (retour au point d’appel) alors queexit
lui va quitter le script (ou le shell). Il est vrai que pour l’analogie c’est bancal carexit
etdie
(avec quelque chose comme `?>>8 || 255` si j’ai bonne mémoire) de perl arrêtent bien le script.“It is seldom that liberty of any kind is lost all at once.” ― David Hume
[^] # Re: Shell
Posté par Marotte ⛧ . Évalué à 4.
Pour ce qui concerne les shells « classiques » (ie: pour simplifier de manière inexacte : sh, bash et ksh, voire zsh…) J’ai lu une fois qu’on pouvait toujours utiliser
||
ou&&
(avec les accolades qui vont bien si nécessaires) pour remplacer, de manière fort élégante je trouve aussi, l’utilisation d’unif
(et desthen
etfi
sans lequel il n’est rien qu’une erreur de syntaxe !). Il semblerait donc que ce soit toujours possible sauf dans le cas (pur ce que serait le plus simple qui ne soit pas faisable avec les deux opérateurs logiques && et ||) d’un :if … then … elif … then … else … fi
Qu’en penses-tu ?
Je n’ai jamais cherché à vérifier de peur de me faire de vilains nœuds indéboulonnables au cerveau droit, que ce soit en cherchant sur le web ou en essayant de réfléchir au problème.
Sinon en dehors de ça, pour ce qui est est des constructions programmatique contre-intuitives mais souvent pratiques et concises, il y a l’utilisation du mécanisme de gestion des exceptions pour implémenter la logique du programme, c’est manifestement une chose souvent bien vue, en Python notamment, bien que ce langage se présente comme le plus « évident », le moins piégeux. Le
try() … catch …
n’a pourtant pas été créé comme une construction usuelle à la base ![^] # Re: Shell
Posté par Benoît Sibaud (site web personnel) . Évalué à 4.
ça paraît jouable (mais faut-il jouer ?) :
(shellcheck ne me dit pas de rejoindre l'enfer des syntaxes honteuses, donc ça doit être portable)
[^] # Re: Shell
Posté par wismerhill . Évalué à 5.
Malheureusement, ce n'est pas tout à fait équivalent, car si, par exemple, le echo "a" échoue, tu va exécuter l'alternative, ce que ne fait pas un if else.
[^] # Re: Shell
Posté par barmic 🦦 . Évalué à 2.
J'ai pas compris ce que tu voulais dire.
https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll
[^] # Re: Shell
Posté par Marotte ⛧ . Évalué à 5.
La construction
try() … catch …
est prévue pour gérer les exceptions, des trucs censés survenir exceptionnellement, en d’autres termes : ne jamais survenir si tout se passe bien. Ce dont je parle c’est le fait d’exploiter ce mécanisme pour des cas prévisibles et attendus. Mais c’est vrai qu’un exemple parlant serait le bienvenue. J’ai pas trop le temps là… Mais tentons toujours :Ce serait par exemple le fait de faire (pseudo code), au lieu d’un :
if isint(userinput): then int_var=userinput; else tell_user_he_is_dumb()
ceci :
try: int_var=userinput; catch 'TypeMismatch': tell_user_he_is_dumb()
Mais je sais pas si c’est plus clair ^^
[^] # Re: Shell
Posté par barmic 🦦 . Évalué à 5.
J'ai mis trop de temps pour éditer mon message
Pour moi c'est un peu comme si tu trouve un vert très joli et que du coup tu repeins TOUT ton logement dans cette couleur du sol au plafond, fenêtres et vaisselles inclue. Il y a des cas où c'est très élégant d'utiliser ce genre de construction, mais quand on l'utilise là où elle n'a pas particulièrement de sens voir qu'elle oblige à faire des circonvolutions ça devient moche.
https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll
[^] # Re: Shell
Posté par Marotte ⛧ . Évalué à 4.
Je suis entièrement d’accord. Le post de Benoît Sibaud illustre très bien ton propos, c’est proprement illisible. Le genre de code qui te fait maudire le développement, ainsi que le développeur qui a écrit ça ainsi que ses parents sur au moins dix générations.
Je cherchais juste à vérifier le caractère strictement indispensable ou pas du
if
. J’ai bien écrit « pouvait «, pas « devait ». ;)wismerhill semble confirmer avec son exemple, on a bien un résultat différent de l’équivalent utilisant les opérateurs booléens donné par Benoît. En toute humilité je n’affirmerai pas qu’il n’est absolument pas possible de faire un équivalent avec des opérateurs logiques, je continuerai juste à continuer penser que c’est effectivement le cas (que c’est impossible).
Par ailleurs, le if then else ne serait que sucre syntaxique, je pense qu’il serait toujours présent dans les langages de programmation, pour la raison que tu donnes.
[^] # Re: Shell
Posté par barmic 🦦 . Évalué à 4.
Pour illustrer le "c'est pas parce qu'il existe déjà une façon de faire qu'on va pas en créer une autre", en perl, tu peux écrire :
Je dois évidemment en oublier, il n'y a pas de forme intrinsèquement supérieure, mais elles tour à tour être plus pratique ou exprimer de manière un peu plus naturelle l'intention (naturelle pour celui qui code au moment où il code).
https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll
[^] # Re: Shell
Posté par Marotte ⛧ . Évalué à 4.
Sur ce point, Perl (le “Pathologically Eclectic Rubbish Lister” selon la page man ^^) est de loin le champion. L’intro de la documentation (
$ perldoc perl
) le dit explicitement : “The Perl motto is "There's more than one way to do it.””.C’est un langage que j’ai très peu utilisé (j’ai pas du en faire depuis deux ou trois ans, au moins), et dont je connais seulement le très très basique. Il y a deux particularités que j’ai retenues en commençant son apprentissage, c’est l’utilisation du principe des pointeurs, comme en C, c’est une gymnastique intellectuelle particulière je trouve, mais on s’y fait, et l’application à l’extrême du principe d’implicite. De mémoire il me semble qu’on peut écrire des trucs du genre
for list print
, ce qui permet d’écrire un code qu’on trouve soit d’une perfection ultime, soit d’une horreur absolue, mais qui ne laisse pas indifférent. :)Mais il permet aussi d’écrire un code atteignant un équilibre entre concision et clarté, et c’est ça qui en fait un excellent langage de script àmha.
C’est strictement le contraire de Python, qui bien qu’il permette de faire la même chose de plusieurs manières, dans une moindre mesure que Perl c’est certain, considère qu’une seule syntaxe est « Pythonique », dans l’esprit du langage en quelque sorte, et qu’elle est donc la syntaxe fortement recommandée pour ceci ou cela. C’est logique étant donné que le but du langage est d’être facile à apprendre, à écrire et à relire. Il cherche l’homogénéité syntaxique pour favoriser la collaboration.
Ceci dit il me semble que c’est avec Python (3) mais pas avec de Perl (5), qu’on peut utiliser n’importe quels caractères Unicode pour nommer les variables/fonctions/class/etc… ce qui peut probablement produire un code avec un fort aspect artistique et une lisibilité très relative ! ^^
Perl est Python sont vraiment antagonistes à ce niveau là.
# continue
Posté par Marotte ⛧ . Évalué à 8.
continue
est bien pratique aussi, pour passer à l’itération suivante en zappant le fin de la boucle pour l’itération en cours.[^] # Re: continue
Posté par Lemnis . Évalué à 3.
À ce sujet, je conseille ce post de blog très pertinent sur l'usage de
break
,continue
etreturn
.[^] # Re: continue
Posté par cg . Évalué à 8.
Chouette billet, merci.
On peut traduire cette phrase :
par :
GOTO 10
❤️[^] # Re: continue
Posté par GG (site web personnel) . Évalué à 2.
Justement non.
Sur ta ligne 10, il n'y a rien qui permette d'identifier que c'est l'arrivée d'éventuels sauts.
Il faudrait un label.
Pourquoi bloquer la publicité et les traqueurs : https://greboca.com/Pourquoi-bloquer-la-publicite-et-les-traqueurs.html
[^] # Re: continue
Posté par Tonton Th (Mastodon) . Évalué à 6.
https://en.wikipedia.org/wiki/COMEFROM
[^] # Re: continue
Posté par SpaceFox (site web personnel, Mastodon) . Évalué à 8.
Et donc si on respecte la qualité de code des gens qui font des méthodes de milliers de lignes :
GOTO TEN
La connaissance libre : https://zestedesavoir.com
[^] # Re: continue
Posté par gUI (Mastodon) . Évalué à 10.
Je fais partie des adeptes du
goto
en C. Justement c'est parfait dans le cas de gestion d'erreurs.En théorie, la théorie et la pratique c'est pareil. En pratique c'est pas vrai.
[^] # Re: continue
Posté par groumly . Évalué à 4.
return, ok, mais break/continue peuvent tres tres souvent (mais pas toujours!) être remplacés de façon bien plus elegante par une combinaison de map/flatMap/findFirst/filter/forEach/que sais je encore. Alors, ok, tout les langages n'ont pas ce genre d'api, mais quand meme java l'a depuis une décennie, on est en droit de penser que c'est une feature de base :)
Sans forcement les interdire, les déconseiller me parait être une bonne idée.
Linuxfr, le portail francais du logiciel libre et du neo nazisme.
[^] # Re: continue
Posté par Marotte ⛧ . Évalué à 3.
C’est bien possible qu’en Python ce soit considérer comme “non pythonic” et que les fonctions que tu site soit recommandée (ça reste à vérifier).
Quand j’ai découvert map() ma vie a changé je dois l’avouer :)
[^] # Re: continue
Posté par barmic 🦦 . Évalué à 3.
Python a plus ou moins 3 syntaxe il me semble :
Il me semble clair que le dernier est mis en avant sur les autres.
https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll
# Bof
Posté par Jean-Baptiste Faure . Évalué à 9.
Pour ce qui est de la simplicité et de l'efficacité, je préfère celle-ci :
Pas vraiment. Je ne comprends pas comment on peut trouver belle une instruction unique qui ne trouve sa justification que par son contexte. Une routine, oui, une instruction, non.
# Tiré d'un script Perl/CGI…
Posté par lasher . Évalué à 5.
… que j'ai écrit il y a looooongtemps. C'était un script qui lisait une image générée dynamiquement et l'affichait pour le client HTTP de l'autre côté (il était invoqué par un script Perl/CGI parent).
[^] # Re: Tiré d'un script Perl/CGI…
Posté par echarp (site web personnel, Mastodon) . Évalué à 3.
Et qui était utile?
Ça ne semble pas super clair comme ça :)
[^] # Re: Tiré d'un script Perl/CGI…
Posté par Gil Cot ✔ (site web personnel, Mastodon) . Évalué à 6.
Si, si, c’est clair pour toute personne qui PERL :)
<>
: ce raccourci est très utilisé et connu dans le langage comme null filehandle ou diamond operator.C’est de toute beauté : on allie cela avec "action-simple [si] condition-simple" vanté par le journal. On peut bien sûr écrire cela en plusieurs lignes, mais bizarrement je n’ai croisé personne qui aime.
“It is seldom that liberty of any kind is lost all at once.” ― David Hume
[^] # Re: Tiré d'un script Perl/CGI…
Posté par karchnu . Évalué à 2.
J'ai toujours beaucoup apprécié Perl pour ce genre de raccourcis.
C'est sa plus grande qualité et son plus grand défaut à la fois.
[^] # Re: Tiré d'un script Perl/CGI…
Posté par lasher . Évalué à 8. Dernière modification le 19 octobre 2023 à 10:49.
Comme dit plus bas,
<>
est un raccourci pour dire<STDIN>
: on lit l'entrée standard (qui est un blob binaire qui contient le GIF à afficher dans mon cas, et qui était généré dynamiquement), et on émet les valeurs sur la sortie standard tant qu'on n'a pas atteintEOF
.En plus verbeux, on pourrait écrire :
En encore plus verbeux :
En encore plus verbeux :
… Et pour un nouveau/une nouvelle venu-e en Perl, c'est sans doute un chouïa plus lisible, mais pas vraiment idiomatique, et franchement pénible pour qui comprend la syntaxe du Perl.
# C'est beau… presque autant que du fonctionnel
Posté par karchnu . Évalué à 3. Dernière modification le 18 novembre 2023 à 16:06.
Ce que tu dis est la raison pour laquelle je trouve magnifique les langages fonctionnels.
Le code de chaque condition particulière est séparé complètement du reste.
Chaque cas est visible d'un coup d'œil, même sans être trop réveillé.
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.