Pourquoi Java 8’s Facultatif ne devrait-il pas être utilisé dans les arguments

J’ai lu sur de nombreux sites Web Facultatif doit être utilisé uniquement comme type de retour et non utilisé dans les arguments de méthode. J’ai du mal à trouver une raison logique pour laquelle. Par exemple, j’ai un morceau de logique qui a 2 parameters facultatifs. Par conséquent, je pense qu’il serait logique d’écrire ma signature de méthode comme ceci (solution 1):

public int calculateSomething(Optional p1, Optional p2 { // my logic } 

De nombreuses pages Web spécifient que Facultatif ne doit pas être utilisé comme argument de méthode. Dans cette optique, je pourrais utiliser la signature de méthode suivante et append un commentaire Javadoc clair pour spécifier que les arguments peuvent être nuls, en espérant que les futurs responsables liront le Javadoc et par conséquent effectueront toujours des vérifications NULL avant d’utiliser les arguments (solution 2) :

 public int calculateSomething(Ssortingng p1, BigDecimal p2) { // my logic } 

Sinon, je pourrais remplacer ma méthode par quatre méthodes publiques pour fournir une interface plus agréable et rendre plus évidentes les options p1 et p2 (solution 3):

 public int calculateSomething() { calculateSomething(null, null); } public int calculateSomething(Ssortingng p1) { calculateSomething(p1, null); } public int calculateSomething(BigDecimal p2) { calculateSomething(null, p2); } public int calculateSomething(Ssortingng p1, BigDecimal p2) { // my logic } 

Maintenant, j’essaie d’écrire le code de la classe qui appelle cette partie logique pour chaque approche. Je récupère d’abord les deux parameters d’entrée d’un autre object qui retourne Optional s et ensuite, j’appelle calculateSomething quelque calculateSomething . Par conséquent, si la solution 1 est utilisée, le code d’appel ressemblerait à ceci:

 Optional p1 = otherObject.getP1(); Optional p2 = otherObject.getP2(); int result = myObject.calculateSomething(p1, p2); 

Si la solution 2 est utilisée, le code d’appel ressemblera à ceci:

 Optional p1 = otherObject.getP1(); Optional p2 = otherObject.getP2(); int result = myObject.calculateSomething(p1.orElse(null), p2.orElse(null)); 

Si la solution 3 est appliquée, je pourrais utiliser le code ci-dessus ou je pourrais utiliser le code suivant (mais c’est beaucoup plus de code):

 Optional p1 = otherObject.getP1(); Optional p2 = otherObject.getP2(); int result; if (p1.isPresent()) { if (p2.isPresent()) { result = myObject.calculateSomething(p1, p2); } else { result = myObject.calculateSomething(p1); } } else { if (p2.isPresent()) { result = myObject.calculateSomething(p2); } else { result = myObject.calculateSomething(); } } 

Donc, ma question est la suivante: pourquoi est-il considéré comme une mauvaise pratique d’utiliser les Optional comme arguments de méthode (voir la solution 1)? Cela me semble être la solution la plus lisible et il est évident que les parameters peuvent être vides / nuls pour les futurs responsables. (Je sais que les concepteurs d’ Optional ne l’utilisaient que comme type de retour, mais je ne trouve aucune raison logique de ne pas l’utiliser dans ce scénario).

Oh, ces styles de codage doivent être pris avec un peu de sel.

  1. (+) Passer un résultat facultatif à une autre méthode, sans parsing sémantique; laisser cela à la méthode, c’est assez bien.
  2. (-) L’utilisation de parameters optionnels entraînant une logique conditionnelle à l’intérieur des méthodes est littéralement contre-productive.
  3. (-) Nécessitant d’emballer un argument dans Facultatif, est sous-optimal pour le compilateur et effectue un emballage inutile.
  4. (-) Par rapport aux parameters nullables, facultatif est plus coûteux.

En général: facultatif unifie deux états, qui doivent être démêlés. Par conséquent, mieux adapté au résultat qu’à l’entrée, pour la complexité du stream de données.

Le meilleur article que j’ai vu sur le sujet a été écrit par Daniel Olszewski :

Bien qu’il puisse être tentant de considérer facultatif pour des parameters de méthode non obligatoires, une telle solution est peu en comparaison avec d’autres alternatives possibles. Pour illustrer le problème, examinez la déclaration de constructeur suivante:

 public SystemMessage(Ssortingng title, Ssortingng content, Optional attachment) { // assigning field values } 

À première vue, cela peut sembler être une bonne décision de conception. Après tout, nous avons explicitement marqué le paramètre de pièce jointe comme facultatif. Cependant, comme pour appeler le constructeur, le code client peut devenir un peu maladroit.

 SystemMessage withoutAttachment = new SystemMessage("title", "content", Optional.empty()); Attachment attachment = new Attachment(); SystemMessage withAttachment = new SystemMessage("title", "content", Optional.ofNullable(attachment)); 

Au lieu de clarifier les choses, les méthodes d’usine de la classe Facultatif ne font que distraire le lecteur. Notez qu’il n’y a qu’un seul paramètre facultatif, mais imaginez en avoir deux ou trois. Oncle Bob ne serait certainement pas fier de ce code 😉

Lorsqu’une méthode peut accepter des parameters facultatifs, il est préférable d’adopter l’approche éprouvée et de concevoir un tel cas en utilisant une surcharge de méthode. Dans l’exemple de la classe SystemMessage, la déclaration de deux constructeurs distincts est supérieure à l’utilisation de facultatif.

 public SystemMessage(Ssortingng title, Ssortingng content) { this(title, content, null); } public SystemMessage(Ssortingng title, Ssortingng content, Attachment attachment) { // assigning field values } 

Ce changement rend le code client beaucoup plus simple et facile à lire.

 SystemMessage withoutAttachment = new SystemMessage("title", "content"); Attachment attachment = new Attachment(); SystemMessage withAttachment = new SystemMessage("title", "content", attachment); 

Il n’y a presque aucune bonne raison de ne pas utiliser Optional tant que parameters. Les arguments contre cela reposent sur des arguments d’autorité (voir Brian Goetz – son argument est que nous ne pouvons pas imposer des optionnels non nuls) ou que les arguments Optional peuvent être nuls (essentiellement le même argument). Bien sûr, toute référence en Java peut être nulle, nous devons encourager les règles appliquées par le compilateur, et non la mémoire du programmeur (ce qui pose problème et ne fait pas évoluer).

Les langages de programmation fonctionnels encouragent les parameters Optional . Une des meilleures façons d’utiliser ceci est d’avoir plusieurs parameters optionnels et d’utiliser liftM2 pour utiliser une fonction en supposant que les parameters ne sont pas vides et renvoient une option (voir http://www.functionaljava.org/javadoc/4.4/functionaljava/fj /data/Option.html#liftM2-fj.F- ). Java 8 a malheureusement implémenté une bibliothèque très limitée prenant en charge les options.

En tant que programmeurs Java, nous ne devrions utiliser null pour interagir avec les bibliothèques existantes.

Ce conseil est une variante du “être aussi peu spécifique que possible en ce qui concerne les entrées et aussi spécifique que possible en ce qui concerne les résultats”.

Généralement, si vous avez une méthode qui prend une valeur non nulle, vous pouvez la mapper sur la valeur Optional , de sorte que la version standard est ssortingctement plus imprécise en ce qui concerne les entrées. Cependant, il y a beaucoup de raisons possibles pour lesquelles vous voudriez exiger un argument Optional néanmoins:

  • vous voulez que votre fonction soit utilisée en conjonction avec une autre API qui retourne une Optional
  • Votre fonction doit renvoyer quelque chose d’autre qu’un élément Optional si la valeur donnée est vide
  • Vous pensez que l’ Optional est si géniale que quiconque utilise votre API doit être tenu d’en apprendre davantage 😉

Le modèle avec Optional est pour un pour éviter de renvoyer null . Il est toujours parfaitement possible de passer null à une méthode.

Bien que ceux-ci ne soient pas encore officiels, vous pouvez utiliser les annotations de style JSR-308 pour indiquer si vous acceptez ou non null valeurs null dans la fonction. Notez que vous devez avoir le bon outil pour l’identifier, et que cela fournirait plus d’une vérification statique qu’une politique d’exécution exécutable, mais cela aiderait.

 public int calculateSomething(@NotNull final Ssortingng p1, @NotNull final Ssortingng p2) {} 

Cela me semble un peu idiot, mais la seule raison pour laquelle je peux penser est que les arguments des objects dans les parameters de la méthode sont déjà facultatifs – ils peuvent être nuls. Forcer quelqu’un à prendre un object existant et à l’envelopper dans une option est donc inutile.

Cela étant dit, enchaîner les méthodes qui prennent / retournent les options est une chose raisonnable à faire, par exemple, peut-être monade.

Mon sharepoint vue est que facultatif devrait être une monade et ceux-ci ne sont pas concevables en Java.

Dans la functional programming, vous traitez des fonctions pures et supérieures qui prennent et composent leurs arguments uniquement en fonction de leur type de domaine métier. Composer des fonctions qui se nourrissent ou doivent être rapscopes au monde réel (les effets secondaires) nécessite l’application de fonctions qui décomposent automatiquement les valeurs des monades représentant le monde extérieur (État, Configuration, Futures, Peut-être, Soit, écrivain, etc …); cela s’appelle le levage. Vous pouvez y voir une sorte de séparation des préoccupations.

Mélanger ces deux niveaux d’abstraction ne facilite pas la lisibilité, il est donc préférable de l’éviter.

Une autre raison d’être prudent quand on passe un paramètre Optional est qu’une méthode devrait faire une chose … Si vous transmettez un paramètre Optional que vous pourriez préférer, vous pouvez faire plus d’une chose, il pourrait être similaire à passer un paramètre booléen.

 public void method(Optional param) { if(param.isPresent()) { //do something } else { //do some other } } 

Je pense que c’est parce que vous écrivez généralement vos fonctions pour manipuler des données, puis les soulevez en Optional utilisant la map et des fonctions similaires. Cela ajoute le comportement Optional par défaut à elle. Bien sûr, il peut y avoir des cas où il est nécessaire d’écrire votre propre fonction auxiliaire qui fonctionne sur Optional .

Je crois que la raison d’être est que vous devez d’abord vérifier si ou non est facultatif lui-même et ensuite essayer d’évaluer la valeur qu’il enveloppe. Trop de validations inutiles.

Les options ne sont pas conçues à cette fin, comme l’explique joliment Brian Goetz .

Vous pouvez toujours utiliser @Nullable pour indiquer qu’un argument de méthode peut être nul. L’utilisation d’une option ne vous permet pas vraiment d’écrire votre logique de méthode plus clairement.

L’acceptation de parameters facultatifs entraîne un enroulement inutile au niveau de l’appelant.

Par exemple dans le cas de:

 public int calculateSomething(Optional p1, Optional p2 {} 

Supposons que vous ayez deux chaînes non nulles (c.-à-d. Renvoyées par une autre méthode):

 Ssortingng p1 = "p1"; Ssortingng p2 = "p2"; 

Vous êtes obligé de les envelopper dans Facultatif même si vous savez qu’ils ne sont pas vides.

Cela devient encore pire lorsque vous devez composer avec d’autres structures “mappables”, c.-à-d. Eithers :

 Either value = compute().right().map((s) -> calculateSomething( < here you have to wrap the parameter in a Optional even if you know it's a string >)); 

ref:

les méthodes ne doivent pas s’attendre à Option en tant que parameters, il s’agit presque toujours d’une odeur de code indiquant une fuite de stream de contrôle de l’appelant à l’appelé, l’appelant ayant la responsabilité de vérifier le contenu d’une option

réf. https://github.com/teamdigitale/digital-citizenship-functions/pull/148#discussion_r170862749

L’utilisation de l’ Optional comme arguments de méthode nécessite également une vérification null sur elle-même. Logique dupliquée inutile. Par exemple:-

 void doSomething(Optional isoOpt, Optional prodOpt){ if(isoOpt != null){ isoOpt.orElse(ISOCode.UNKNOWN); } if(prodOpt != null){ prodOpt.orElseThrow(NullPointerException::new); } //more instructions } 

Imaginez si, le nombre de parameters Optional augmente. Si vous oubliez la vérification NULL, il est possible que NPE soit lancé à l’exécution.

De plus, si votre méthode est publique, le client sera obligé d’envelopper ses parameters dans Optionals, ce qui sera un fardeau, surtout si le nombre d’arguments augmente.

Une autre approche, ce que vous pouvez faire, c’est

 // get your optionals first Optional p1 = otherObject.getP1(); Optional p2 = otherObject.getP2(); // bind values to a function Supplier calculatedValueSupplier = () -> { // your logic here using both optional as state} 

Une fois que vous aurez créé une fonction (fournisseur dans ce cas), vous pourrez la faire passer comme toute autre variable et pouvoir l’appeler en utilisant

 calculatedValueSupplier.apply(); 

L’idée ici, que vous ayez ou non une valeur optionnelle, sera le détail interne de votre fonction et ne sera pas en paramètre. Penser les fonctions lorsque l’on pense à optionnel en tant que paramètre est en fait une technique très utile que j’ai trouvée.

En ce qui concerne votre question de savoir si vous devez réellement le faire ou non, cela dépend de vos préférences, mais comme d’autres l’ont dit, cela rend votre API moche pour le moins qu’on puisse dire.

Au début, je préférais également passer en tant que paramètre Optionals, mais si vous passez d’une perspective API-Designer à une perspective API-User, vous voyez les inconvénients.

Pour votre exemple, où chaque paramètre est facultatif, je suggère de changer la méthode de calcul en une classe comme suit:

 Optional p1 = otherObject.getP1(); Optional p2 = otherObject.getP2(); MyCalculator mc = new MyCalculator(); p1.map(mc::setP1); p2.map(mc::setP2); int result = mc.calculate(); 

Je sais que cette question concerne davantage l’opinion que les faits. Mais récemment, je suis passé d’un développeur .net à un développeur java. Aussi, je préférerais dire ceci comme un commentaire, mais comme mon niveau de points ne me permet pas de commenter, je suis obligé de mettre cela au lieu de cela.

Ce que je faisais, ce qui m’a bien servi de règle. Utiliser les options pour les types de retour et utiliser uniquement les options comme parameters, si j’ai besoin à la fois de la valeur facultative et de la météo ou non, la valeur facultative de la méthode a été utilisée.

Si je me soucie seulement de la valeur, je vérifie isPresent avant d’appeler la méthode, si j’ai une sorte de journalisation ou une logique différente dans la méthode qui dépend de la valeur, alors je passerai volontiers le facultatif.