Les getters Java 8 doivent-ils renvoyer le type facultatif?

288

Optional Le type introduit dans Java 8 est une nouveauté pour de nombreux développeurs.

Une méthode getter renvoyant un Optional<Foo>type à la place du classique est-elle Fooune bonne pratique? Supposons que la valeur puisse être null.

leonprou
la source
8
Bien que cela soit susceptible d'attirer des réponses d'opinion, c'est une bonne question. J'attends avec impatience une réponse avec des faits réels sur le sujet.
Justin
8
La question est de savoir si la nullité est inévitable. Un composant peut avoir une propriété qui peut être nulle, mais le programmeur utilisant ce composant peut quand même décider de garder strictement cette propriété non null. Le programmeur ne devrait donc pas avoir à s'occuper Optionalalors. Ou, en d'autres termes, nullreprésente vraiment l'absence d'une valeur comme avec le résultat d'une recherche (le cas Optionaléchéant) ou n'est nullqu'un membre de l'ensemble des valeurs possibles.
Holger
1
Voir aussi la discussion sur les @NotNullannotations: stackoverflow.com/q/4963300/873282
koppor

Réponses:

515

Bien sûr, les gens feront ce qu'ils veulent. Mais nous avions une intention claire lors de l'ajout de cette fonctionnalité, et ce n'était pas un type peut-être à usage général, autant de personnes auraient souhaité que nous le fassions. Notre intention était de fournir un mécanisme limité pour les types de retour de méthode de bibliothèque où il devait y avoir un moyen clair de représenter "aucun résultat", et son utilisation nullétait extrêmement susceptible de provoquer des erreurs.

Par exemple, vous ne devriez probablement jamais l'utiliser pour quelque chose qui renvoie un tableau de résultats ou une liste de résultats; renvoie à la place un tableau ou une liste vide. Vous ne devriez presque jamais l'utiliser comme champ de quelque chose ou paramètre de méthode.

Je pense que l'utiliser régulièrement comme valeur de retour pour les getters serait certainement une surutilisation.

Il n'y a rien de mal à Option qu'il faut éviter, ce n'est tout simplement pas ce que beaucoup de gens souhaitent, et en conséquence, nous étions assez préoccupés par le risque de surutilisation zélée.

(Annonce de la fonction publique: NE JAMAIS appeler à Optional.getmoins que vous pouvez prouver qu'il ne sera jamais nul, au lieu d' utiliser l' une des méthodes sûres comme orElseou ifPresent. Avec le recul, nous aurions dû appelé getquelque chose comme getOrElseThrowNoSuchElementExceptionou quelque chose qui a rendu beaucoup plus clair que cela était une méthode très dangereuse qui a sapé tout le but de Optionalen premier lieu. Leçon apprise (MISE À JOUR: Java 10 a Optional.orElseThrow(), qui est sémantiquement équivalent à get(), mais dont le nom est plus approprié.))

Brian Goetz
la source
24
(Concernant la dernière partie)… et quand nous sommes confiants, que la valeur n'est jamais que nullnous pourrions utiliser orElseThrow(AssertionError::new), ahem ou orElseThrow(NullPointerException::new)
Holger
25
Qu'auriez-vous fait différemment si votre intention avait été d'introduire un type polyvalent Peut-être ou Quelque chose? Existe-t-il un moyen pour Optionnel de ne pas convenir, ou est-ce simplement que l'introduction d'Optionals partout dans une nouvelle API la rendrait non Java-ish?
David Moles
22
Par "type à usage général", je voulais dire l'intégrer dans le système de type du langage, plutôt que de fournir une classe de bibliothèque qui le rapproche. (Certaines langues ont des types pour T? (T ou null) et T! (T non-nullable)) Facultatif est juste une classe; nous ne pouvons pas faire de conversions implicites entre Foo et <Foo> facultatif comme nous pourrions le faire avec le support de la langue.
Brian Goetz
11
Je me demande depuis un moment pourquoi l'accent dans le monde Java était sur l'option facultative et non sur une meilleure analyse statique. L'option facultative présente certains avantages, mais l'énorme avantage qui en nulldécoule est la compatibilité descendante; Map::getrenvoie un nullable V, pas un Optional<V>, et cela ne changera jamais. Il aurait cependant pu être facilement annoté @Nullable. Maintenant, nous avons deux façons d'exprimer le manque de valeur, plus moins d'incitation à vraiment réaliser une analyse statique, ce qui semble être une pire position.
yshavit
21
Je ne savais pas que l'utiliser comme valeur de retour pour une propriété n'était pas ce que vous vouliez avant d'avoir lu cette réponse ici sur StackOverflow. En fait, j'ai été amené à croire que c'était exactement ce que vous vouliez tous après avoir lu cet article sur le site Web d'Oracle: oracle.com/technetwork/articles/java/… Merci de clarifier
Jason Thompson
73

Après avoir fait mes propres recherches, je suis tombé sur un certain nombre de choses qui pourraient suggérer quand cela est approprié. La plus fiable étant la citation suivante d'un article Oracle:

"Il est important de noter que l'intention de la classe Optional n'est pas de remplacer chaque référence null unique . Au lieu de cela, son but est d'aider à concevoir des API plus compréhensibles de sorte qu'en lisant simplement la signature d'une méthode, vous pouvez dire si vous peut s'attendre à une valeur facultative. Cela vous oblige à déballer activement une valeur facultative pour faire face à l'absence de valeur. " - Fatigué des exceptions de pointeur nul? Pensez à utiliser Java SE 8 en option!

J'ai également trouvé cet extrait de Java 8 en option: comment l'utiliser

"Facultatif n'est pas destiné à être utilisé dans ces contextes, car il ne nous rapportera rien:

  • dans la couche de modèle de domaine (non sérialisable)
  • dans les DTO (même raison)
  • dans les paramètres d'entrée des méthodes
  • dans les paramètres du constructeur "

Ce qui semble également soulever des points valables.

Je n'ai pas pu trouver de connotations négatives ou de drapeaux rouges suggérant que cela Optionaldevrait être évité. Je pense que l'idée générale est, si elle est utile ou améliore la convivialité de votre API, utilisez-la.

Justin
la source
11
stackoverflow.com/questions/25693309 - il semble que Jackson le supporte déjà, donc "non sérialisable" ne passe plus comme une raison valable :)
Vlasec
1
Je suggère d'avoir votre réponse pour expliquer pourquoi l'utilisation Optionaldans les paramètres d'entrée des méthodes (plus spécifiquement les constructeurs) "ne nous achètera rien". dolszewski.com/java/java-8-optional-use-cases contient une belle explication.
Gili
J'ai constaté que le currying des résultats facultatifs qui doivent être transformés en d'autres API nécessite un paramètre facultatif. Le résultat est une API assez compréhensible. Voir stackoverflow.com/a/31923105/105870
Karl the Pagan
1
Le lien est rompu. Pourriez-vous mettre à jour la réponse?
softarn
2
Le problème est qu'il n'améliore souvent pas l'API, malgré les meilleures intentions du développeur. J'ai été des exemples de collecte de mauvaises utilisations d'Optional , toutes tirées du code de production, que vous pouvez consulter.
MiguelMunoz
20

Je dirais qu'en général, c'est une bonne idée d'utiliser le type facultatif pour les valeurs de retour qui peuvent être nullables. Cependant, par rapport aux frameworks, je suppose que le remplacement des getters classiques par des types facultatifs causera beaucoup de problèmes lorsque vous travaillez avec des frameworks (par exemple, Hibernate) qui reposent sur des conventions de codage pour les getters et les setters.

Claas Wilke
la source
14
Ce conseil est exactement le genre de chose que je voulais dire par «nous sommes préoccupés par le risque de surutilisation zélée» dans stackoverflow.com/a/26328555/3553087 .
Brian Goetz
13

La raison a Optionalété ajoutée à Java parce que:

return Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
    .stream()
    .filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
    .filter(m ->  Arrays.equals(m.getParameterTypes(), parameterClasses))
    .filter(m -> Objects.equals(m.getReturnType(), returnType))
    .findFirst()
    .getOrThrow(() -> new InternalError(...));

est plus propre que cela:

Method matching =
    Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
    .stream()
    .filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
    .filter(m ->  Arrays.equals(m.getParameterTypes(), parameterClasses))
    .filter(m -> Objects.equals(m.getReturnType(), returnType))
    .getFirst();
if (matching == null)
  throw new InternalError("Enclosing method not found");
return matching;

Mon point est qu'Optional a été écrit pour supporter la programmation fonctionnelle , qui a été ajoutée à Java en même temps. (L'exemple est fourni par un blog de Brian Goetz . Un meilleur exemple pourrait utiliser la orElse()méthode, car ce code lèvera quand même une exception, mais vous obtenez l'image.)

Mais maintenant, les gens utilisent Facultatif pour une raison très différente. Ils l'utilisent pour corriger une faille dans la conception du langage. La faille est la suivante: il n'y a aucun moyen de spécifier quels paramètres et valeurs de retour d'une API sont autorisés à être nuls. Cela peut être mentionné dans les javadocs, mais la plupart des développeurs n'écrivent même pas de javadocs pour leur code, et peu de gens vérifieront les javadocs pendant qu'ils écrivent. Cela conduit donc à beaucoup de code qui vérifie toujours les valeurs nulles avant de les utiliser, même si elles ne peuvent souvent pas être nulles car elles ont déjà été validées à plusieurs reprises neuf ou dix fois dans la pile des appels.

Je pense qu'il y avait une réelle soif de résoudre cette faille, car tant de personnes qui ont vu la nouvelle classe Optional ont supposé que son but était d'ajouter de la clarté aux API. C'est pourquoi les gens posent des questions comme «les getters devraient-ils retourner les options?» Non, ils ne devraient probablement pas, sauf si vous vous attendez à ce que le getter soit utilisé dans la programmation fonctionnelle, ce qui est très peu probable. En fait, si vous regardez où Optional est utilisé dans l'API Java, c'est principalement dans les classes Stream, qui sont au cœur de la programmation fonctionnelle. (Je n'ai pas vérifié très attentivement, mais les classes Stream peuvent être le seul endroit où elles sont utilisées.)

Si vous prévoyez d'utiliser un getter dans un peu de code fonctionnel, il peut être judicieux d'avoir un getter standard et un second qui retourne Facultatif.

Oh, et si vous avez besoin que votre classe soit sérialisable, vous ne devez absolument pas utiliser Facultatif.

Les options sont une très mauvaise solution à la faille de l'API car a) elles sont très verbeuses et b) elles n'ont jamais été conçues pour résoudre ce problème en premier lieu.

Une bien meilleure solution à la faille API est le Nullness Checker . Il s'agit d'un processeur d'annotations qui vous permet de spécifier les paramètres et les valeurs de retour autorisés à être annulés en les annotant avec @Nullable. De cette façon, le compilateur peut analyser le code et déterminer si une valeur qui peut réellement être nulle est passée à une valeur où null n'est pas autorisé. Par défaut, il suppose que rien ne peut être nul à moins qu'il ne soit annoté. De cette façon, vous n'avez pas à vous soucier des valeurs nulles. La transmission d'une valeur nulle à un paramètre entraînera une erreur de compilation. Tester un objet pour null qui ne peut pas être null produit un avertissement du compilateur. Cela a pour effet de faire passer NullPointerException d'une erreur d'exécution à une erreur de compilation.

Cela change tout.

Quant à vos getters, n'utilisez pas facultatif. Et essayez de concevoir vos classes afin qu'aucun des membres ne puisse être nul. Et peut-être essayez d'ajouter le Nullness Checker à votre projet et de déclarer vos getters et setter paramètres @Nullable s'ils en ont besoin. Je ne l'ai fait qu'avec de nouveaux projets. Il génère probablement de nombreux avertissements dans les projets existants écrits avec de nombreux tests superflus pour null, il peut donc être difficile de le mettre à niveau. Mais cela entraînera également de nombreux bugs. J'aime cela. Mon code est beaucoup plus propre et plus fiable à cause de cela.

(Il existe également un nouveau langage qui résout ce problème. Kotlin, qui compile en code octet Java, vous permet de spécifier si un objet peut être nul lorsque vous le déclarez. C'est une approche plus propre.)

Addendum à la publication originale (version 2)

Après y avoir longuement réfléchi, je suis parvenu à contrecœur à la conclusion qu'il est acceptable de retourner Facultatif à une condition: que la valeur récupérée soit en fait nulle. J'ai vu beaucoup de code où les gens retournent régulièrement Facultatif des getters qui ne peuvent pas retourner null. Je vois cela comme une très mauvaise pratique de codage qui ne fait qu'ajouter de la complexité au code, ce qui rend les bogues plus probables. Mais lorsque la valeur renvoyée peut effectivement être nulle, allez-y et encapsulez-la dans un Facultatif.

Gardez à l'esprit que les méthodes conçues pour la programmation fonctionnelle et qui nécessitent une référence de fonction seront (et devraient) être écrites sous deux formes, dont l'une utilise Facultatif. Par exemple, Optional.map()et les Optional.flatMap()deux prennent des références de fonction. Le premier prend une référence à un getter ordinaire, et le second en prend un qui renvoie Facultatif. Vous ne rendez donc service à personne en renvoyant un Facultatif où la valeur ne peut pas être nulle.

Cela dit, je vois toujours que l'approche utilisée par le vérificateur de nullité est la meilleure façon de traiter les valeurs nulles, car elles transforment les exceptions NullPointerException des bogues d'exécution pour compiler des erreurs de temps.

MiguelMunoz
la source
2
Cette réponse me semble la plus appropriée. Facultatif n'a été ajouté dans java 8 que lorsque des flux ont été ajoutés. Et seules les fonctions de flux sont facultatives pour autant que je sache.
Archit
1
Je pense que c'est l'une des meilleures réponses. Les gens savent ce qui est facultatif et comment cela fonctionne. Mais la partie la plus déroutante est / était où l'utiliser et comment l'utiliser. en lisant ceci, il efface beaucoup de doutes.
ParagFlume
3

Si vous utilisez des sérialiseurs modernes et d'autres frameworks qui comprennent, Optionalj'ai trouvé que ces directives fonctionnent bien lors de l'écriture de Entitybeans et de couches de domaine:

  1. Si la couche de sérialisation (généralement une base de données) autorise une nullvaleur pour une cellule dans la colonne BARdu tableau FOO, le getter Foo.getBar()peut retourner en Optionalindiquant au développeur que cette valeur peut raisonnablement être considérée comme nulle et ils doivent gérer cela. Si la base de données garantit que la valeur ne sera pas nulle, le getter ne doit pas envelopper cela dans un Optional.
  2. Foo.bardevrait être privateet ne pas être Optional. Il n'y a vraiment aucune raison que ce soit le Optionalcas private.
  3. Le passeur Foo.setBar(String bar)doit prendre le type de baret non Optional . Si vous pouvez utiliser un nullargument, dites-le dans le commentaire JavaDoc. Si ce n'est pas OK d'utiliser nullune IllegalArgumentExceptionou une logique métier appropriée, à mon humble avis, plus approprié.
  4. Les constructeurs n'ont pas besoin d' Optionalarguments (pour des raisons similaires au point 3). En général, j'inclus uniquement des arguments dans le constructeur qui doivent être non nuls dans la base de données de sérialisation.

Pour rendre ce qui précède plus efficace, vous voudrez peut-être éditer vos modèles IDE pour générer des getters et les modèles correspondants pour toString(), equals(Obj o)etc. ou utiliser des champs directement pour ceux-ci (la plupart des générateurs IDE traitent déjà les nulls).

marque
la source