web-dev-qa-db-fra.com

Pourquoi ne pas utiliser Java 8 facultatif dans les arguments?

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

public int calculateSomething(Optional<String> p1, Optional<BigDecimal> p2 {
    // my logic
}

De nombreuses pages Web spécifiant Facultatif ne doivent pas être utilisées comme arguments de méthode. Dans cet esprit, je pourrais utiliser la signature de méthode suivante et ajouter un commentaire Javadoc clair pour indiquer que les arguments peuvent être nuls, en espérant que les futurs responsables liront le Javadoc et effectueront donc toujours des vérifications nulles avant d'utiliser les arguments (solution 2) :

public int calculateSomething(String 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 la rendre plus évidente. P1 et p2 sont facultatifs (solution 3):

public int calculateSomething() {
    calculateSomething(null, null);
}

public int calculateSomething(String p1) {
    calculateSomething(p1, null);
}

public int calculateSomething(BigDecimal p2) {
    calculateSomething(null, p2);
}

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

Maintenant, j'essaie d'écrire le code de la classe qui appelle cette logique pour chaque approche. Je récupère d'abord les deux paramètres d'entrée d'un autre objet qui retourne Optionals, puis j'appelle calculateSomething. Par conséquent, si la solution 1 est utilisée, le code appelant devrait ressembler à ceci:

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

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

Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> 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 le suivant (mais c'est beaucoup plus de code):

Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> 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();
    }
}

Ma question est donc la suivante: pourquoi est-il considéré comme une mauvaise pratique d'utiliser Optionals comme arguments de méthode (voir la solution 1)? Cela me semble la solution la plus lisible et il est tout à fait évident que les paramètres pourraient être vides/nuls pour les futurs responsables. (Je sais que les concepteurs de Optional avaient prévu de l'utiliser uniquement comme type de retour, mais je ne trouve aucune raison logique de ne pas l'utiliser dans ce scénario).

269
Neil Stevens

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

  1. (+) Passer un résultat facultatif à une autre méthode, sans analyse sémantique; laisser cela à la méthode, est tout à fait correct.
  2. (-) L'utilisation de paramètres facultatifs entraînant une logique conditionnelle dans les méthodes est littéralement contre-productive.
  3. (-) La nécessité de compresser un argument dans un facultatif est sous-optimale pour le compilateur et effectue un wrapping inutile.
  4. (-) En comparaison avec les paramètres 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’à la saisie, à la complexité du flux de données.

138
Joop Eggen

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

Bien qu’il puisse être tentant de considérer les paramètres de méthode facultatifs comme facultatifs, cette solution n’est guère convaincante en comparaison avec d’autres solutions possibles. Pour illustrer le problème, examinez la déclaration de constructeur suivante:

public SystemMessage(String title, String content, Optional<Attachment> attachment) {
    // assigning field values
}

À première vue, cela peut sembler être une bonne décision de conception. Après tout, nous 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 fournir des précisions, les méthodes d'usine de la méthode facultative .__ la classe ne fait que distraire le lecteur. Notez qu’il n’ya qu’une option paramètre, mais imaginez en avoir deux ou trois. Oncle Bob définitivement ne serait pas fier de ce code ????

Quand une méthode peut accepter des paramètres optionnels, il est préférable d’adopter l’approche éprouvée et de concevoir un tel cas en utilisant la méthode surcharge. Dans l'exemple de la classe SystemMessage, déclarez deux constructeurs distincts sont supérieurs à facultatif.

public SystemMessage(String title, String content) {
    this(title, content, null);
}

public SystemMessage(String title, String content, Attachment attachment) {
    // assigning field values
}

Cette modification rend le code client beaucoup plus simple et plus facile à lire.

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

Il n'y a presque aucune bonne raison de ne pas utiliser Optional en tant que paramètres. Les arguments contre ceci reposent sur des arguments d'autorité (voir Brian Goetz - son argument est que nous ne pouvons pas appliquer de fonctions non-optionnelles non nulles) ou que les arguments Optional peuvent être nuls (essentiellement les mêmes arguments). Bien sûr, toute référence en Java peut être nulle, nous devons encourager les règles à être appliquées par le compilateur, et non par la mémoire des programmeurs (ce qui est problématique et ne s'adapte pas à l'échelle). 

Les langages de programmation fonctionnels encouragent les paramètres Optional. L’un des meilleurs moyens d’utiliser cette méthode consiste à avoir plusieurs paramètres facultatifs et à utiliser liftM2 pour utiliser une fonction, à condition que les paramètres ne soient pas vides et renvoie un paramètre facultatif (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 supportant les options.

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

64
Mark Perry

Ce conseil est une variante de la règle empirique "soyez 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 plain non nulle, vous pouvez la mapper sur la variable Optional, de sorte que la version simple est strictement plus non spécifique en ce qui concerne les entrées. Cependant il existe de nombreuses raisons pour lesquelles vous voudriez quand même exiger un argument Optional:

  • vous voulez que votre fonction soit utilisée avec une autre API qui retourne une Optional
  • Votre fonction devrait retourner autre chose qu'une Optional vide si la valeur donnée est vide
  • Vous pensez que Optional est tellement génial que quiconque utilise votre API devrait être obligé de se renseigner à ce sujet ;-)
10
llogiq

Le modèle avec Optional évite que retournantnull. Il est toujours parfaitement possible de passer null à une méthode.

Bien que celles-ci ne soient pas encore vraiment officielles, vous pouvez utiliser style JSR-308 annotations pour indiquer si vous acceptez ou non les valeurs null dans la fonction. Notez que vous devez disposer des outils adéquats pour l'identifier et fournir une vérification statique plus qu'une stratégie d'exécution exécutable, mais cela aiderait.

public int calculateSomething(@NotNull final String p1, @NotNull final String p2) {}
8
Makoto

Cela me semble un peu bête, mais la seule raison pour laquelle je peux penser est que les arguments d'objet dans les paramètres de méthode sont déjà optionnels - ils peuvent être nuls. Par conséquent, forcer quelqu'un à prendre un objet existant et à le placer dans une option est en quelque sorte inutile. 

Cela étant dit, lier des méthodes qui prennent/retournent des options est une chose raisonnable à faire, par exemple. Peut-être monade.

6
Steve B.

Ma position est que Optional devrait être une monade et que ceux-ci ne sont pas concevables en Java.

En programmation fonctionnelle, vous utilisez des fonctions pures et d'ordre supérieur qui prennent et composent leurs arguments uniquement en fonction de leur "type de domaine commercial". La composition de fonctions qui se nourrissent du calcul ou qui doivent être signalées au monde réel (appelés effets secondaires) nécessite l’application de fonctions permettant de décomposer automatiquement les valeurs des monades représentant le monde extérieur Futures, Peut-être, Soit, Écrivain, etc ...); c'est ce qu'on appelle lever. Vous pouvez penser à cela comme une sorte de séparation des préoccupations.

Mélanger ces deux niveaux d'abstraction ne facilite pas la lisibilité, il vaut donc mieux l'éviter.

3
Eddy

Les options ne sont pas conçues à cet effet, comme l'explique joliment Brian Goetz .

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

2
Kieran

Une autre raison de rester prudent lorsque vous passez un paramètre Optional est qu'une méthode doit faire une chose ... Si vous passez un paramètre Optional, vous pouvez faire plus d'une chose.

public void method(Optional<MyClass> param) {
     if(param.isPresent()) {
         //do something
     } else {
         //do some other
     }
 }
2
Pau

Je pense que c'est parce que vous écrivez habituellement vos fonctions pour manipuler des données, puis vous les portez à Optional à l'aide de map et de fonctions similaires. Cela ajoute le comportement par défaut Optional à celui-ci . Bien sûr, il peut y avoir des cas où il est nécessaire d'écrire votre propre fonction auxiliaire qui fonctionne sur Optional.

1
Danil Gaponov

Je pense que la raison en est que vous devez d’abord vérifier si Facultatif est null lui-même, puis essayer d’évaluer la valeur qu’il recouvre. Trop de validations inutiles.

1
Macchiatow

Consultez le JavaDoc dans JDK10, https://docs.Oracle.com/javase/10/docs/api/Java/util/Optional.html , une note d'API est ajoutée:

Note API: Facultatif est principalement destiné à être utilisé en tant que type de retour de méthode où il est clairement nécessaire de représenter "aucun résultat" et où l'utilisation de null est susceptible de provoquer des erreurs.

1
Xiaolong

L'acceptation de Facultatif en tant que paramètres entraîne un retour à la ligne inutile au niveau de l'appelant.

Par exemple dans le cas de:

public int calculateSomething(Optional<String> p1, Optional<BigDecimal> p2 {}

Supposons que vous ayez deux chaînes non nulles (c'est-à-dire renvoyées par une autre méthode):

String p1 = "p1"; 
String p2 = "p2";

Vous êtes obligé de les envelopper en option 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'est-à-dire. Eithers :

Either<Error, String> 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 devraient pas attendre Option en tant que paramètres, il s'agit presque toujours d'un odeur de code indiquant une fuite du flux de commande de l'appelant à l'appelé, il devrait être de la responsabilité de l'appelant de vérifier le contenu d'une option

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

1
gpilotino

Au début, j'ai également préféré passer Optionals en tant que paramètre, mais si vous passez d'une perspective API-Designer à une perspective API-Utilisateur, vous verrez les inconvénients. 

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

Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();

MyCalculator mc = new MyCalculator();
p1.map(mc::setP1);
p2.map(mc::setP2);
int result = mc.calculate();
0
Torsten

Une autre approche, ce que vous pouvez faire est 

// get your optionals first
Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();

// bind values to a function
Supplier<Integer> calculatedValueSupplier = () -> { // your logic here using both optional as state}

Une fois que vous avez créé une fonction (fournisseur dans ce cas), vous pourrez la transmettre comme n'importe quelle autre variable et pourrez l'appeler à l'aide de

calculatedValueSupplier.apply();

L’idée ici de savoir si vous avez une valeur optionnelle ou non sera un détail interne de votre fonction et ne sera pas paramétré. Penser les fonctions en pensant au paramètre facultatif est en fait une technique très utile que j'ai trouvée.

En ce qui concerne votre question, si vous devriez 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 très moche pour le moins.

0
Swaraj Yadav

L'utilisation de Optional comme arguments de méthode doit également être vérifiée par null. Duplication inutile de la logique. Par exemple:-

void doSomething(Optional<ISOCode> isoOpt, Optional<Product> prodOpt){

    if(isoOpt != null){
       isoOpt.orElse(ISOCode.UNKNOWN);
    }

    if(prodOpt != null){
       prodOpt.orElseThrow(NullPointerException::new);
    }

    //more instructions
}

Imaginez si le nombre de paramètres Optional augmente. Si vous oubliez la vérification nulle, des NPE seront éventuellement émis au moment de l'exécution.

De plus, si votre méthode est publique, le client sera forcé d'envelopper ses paramètres dans Optionals, ce qui sera un fardeau, surtout si le nombre d'arguments augmente.

0
Awan Biru

En effet, les exigences d’un utilisateur d’API et d’un développeur d’API sont différentes. 

Un développeur est responsable de fournir une spécification précise et une implémentation correcte. Par conséquent, si le développeur sait déjà qu’un argument est facultatif, l’implémentation doit le traiter correctement, qu’il soit null ou facultatif. L'API doit être aussi simple que possible pour l'utilisateur, et null est le plus simple.

D'autre part, le résultat est transmis du développeur de l'API à l'utilisateur. Cependant, si la spécification est complète et détaillée, il est toujours possible que l'utilisateur n'en soit pas conscient ou soit simplement paresseux. Dans ce cas, le résultat facultatif oblige l'utilisateur à écrire du code supplémentaire pour traiter un résultat éventuellement vide.

0
speedogoo

Je sais que cette question concerne davantage l'opinion que les faits concrets. Mais je suis récemment passé du statut de développeur .net à celui de Java et je n’ai donc rejoint que récemment la partie Optional. Aussi, je préférerais dire cela comme un commentaire, mais puisque mon niveau de points ne me permet pas de commenter, je suis obligé de mettre cela comme une réponse à la place.

Ce que je faisais, qui m’a bien servi en règle générale. Est à utiliser Optionals pour les types de retour et à utiliser uniquement Optionals en tant que paramètres, si j'ai besoin à la fois de la valeur de Optional, et de la météo ou non de Facultatif ayant une valeur dans la méthode.

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 si la valeur existe, alors je passerai avec plaisir l'option.

0
Blair

Bien que ce sujet soit ancien, cela m'intéresse pour le moment.

Personnellement, je préfère la solution 3 tout en fournissant un argument à une méthode est toujours considéré comme non nul (j'aime bien ajouter lomboks @NonNull). Donc, si vous voulez vraiment accepter tous vos 4 cas, pourquoi ne pas utiliser un objet paramètre? Peut-être que votre otherObject peut satisfaire? En général, je voudrais savoir si je veux vraiment fournir une API qui accepte ces valeurs (facultatives) ou puis-je refaire une architecture pour éviter les paramètres facultatifs? Si vous pensez avoir vraiment besoin de paramètres optionnels, je suppose que vous cassez SRP.

0
Stephan Prätsch