web-dev-qa-db-fra.com

Java 8 getters doivent-ils renvoyer un type facultatif?

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

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

253
leonprou

Bien sûr, les gens vont faire ce qu'ils veulent. Mais nous avions une intention claire lors de l’ajout de cette fonctionnalité, et c’était pas d’être un type à usage général Maybe, autant que beaucoup 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 exister un moyen clair de représenter "aucun résultat", et en utilisant null pour ce type de résultats, une probabilité écrasante 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; retourne plutôt un tableau ou une liste vide. Vous ne devriez presque jamais l'utiliser comme champ de quelque chose ou comme paramètre de méthode.

Je pense qu'en l'utilisant régulièrement comme valeur de retour pour les accesseurs, il s'agirait certainement d'une surutilisation.

Il n'y a rien faux avec Facultatif qu'il faille l'éviter, ce n'est tout simplement pas ce que beaucoup de gens souhaitent, et nous étions donc assez inquiets du risque de surutilisation par le zèle.

(Avis de service public: NEVER appelez Optional.get à moins que vous ne puissiez prouver qu'il ne sera jamais nul, utilisez plutôt l'une des méthodes sûres comme orElse ou ifPresent Rétrospectivement, nous aurions dû appeler get quelque chose comme getOrElseThrowNoSuchElementException ou quelque chose qui montre clairement qu'il s'agit d'une méthode très dangereuse qui compromet le but même de Optional. Leçon apprise. (UPDATE: Java 10 a Optional.orElseThrow(), ce qui est sémantiquement équivalent à get(), mais dont le nom est plus approprié.))

453
Brian Goetz

Après avoir fait mes propres recherches, j'ai découvert un certain nombre de choses qui pourraient suggérer lorsque cela est approprié. La citation suivante d'un article Oracle est la plus fiable:

"Il est important de noter que l'intention de la classe Optional est de ne pas remplacer chaque référence nulle . L’objectif est d’aider à la conception d’API plus compréhensibles , de sorte qu’il suffit de lire la signature d’une méthode pour savoir si vous pouvez vous attendre à une valeur optionnelle. vous oblige à déballer activement une option pour gérer l'absence de valeur. " - Fatigué des exceptions de pointeur nul? Envisagez d'utiliser Java facultatif de la SE 8!

J'ai aussi trouvé cet extrait de Java 8 facultatif: comment l'utiliser

"Facultatif n'est pas destiné à être utilisé dans ces contextes, car il ne nous achètera 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 ni de drapeaux rouges suggérant d'éviter Optional. Je pense que l’idée générale est la suivante: si cela vous aide ou améliore la convivialité de votre API, utilisez-la.

67
Justin

Je dirais qu'en général, c'est une bonne idée d'utiliser le type optionnel pour les valeurs de retour pouvant être nullables. Cependant, w.r.t. Pour les frameworks, je suppose que le remplacement des getters classiques par des types optionnels causera beaucoup de problèmes lorsqu’on travaille avec des frameworks (par exemple, Hibernate) qui reposent sur des conventions de codage pour les getters et les setters.

18
Claas Wilke

La raison pour laquelle Optional a été ajoutée à Java est la suivante:

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 que Facultatif a été écrit pour prendre en charge la programmation fonctionnelle, qui a été ajouté à Java en même temps. (L'exemple est fourni gracieusement par un blog de Brian Goetz . Un meilleur exemple pourrait utiliser la méthode orElse(), car ce code lève de toute façon 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 de la langue. La faille est la suivante: il n’existe aucun moyen de spécifier les paramètres et les valeurs de retour d’une API autorisés à être NULL. 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érifient les javadocs à mesure qu'ils écrivent. Cela conduit donc à beaucoup de code qui vérifie toujours les valeurs NULL avant de les utiliser, même si elles ne peuvent souvent pas être null car elles ont déjà été validées à plusieurs reprises neuf ou dix fois plus loin dans la pile d'appels.

Je pense qu'il y avait une réelle soif de résoudre ce problème, car beaucoup de personnes qui ont vu la nouvelle classe Optional ont supposé que son objectif était de clarifier les API. Pourquoi les gens posent-ils des questions du type "les getters devraient-ils renvoyer les optionnels?" 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 de flux pourraient être le lieu seulement ​​où elles sont utilisées.)

Si vous envisagez d’utiliser un getter dans un peu de code fonctionnel, il peut être judicieux d’avoir un getter standard et un second renvoyant Optional.

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 prolixes et b) Elles n’ont jamais été conçues pour résoudre ce problème.

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

Cela change tout.

Quant à vos accesseurs, n'utilisez pas Facultatif. Et essayez de concevoir vos classes de manière à ce qu'aucun des membres ne puisse être null. Et essayez peut-être d’ajouter le vérificateur de nullité à votre projet et de déclarer vos getters et paramètres de réglage @Nullable s’ils en ont besoin. Je l'ai seulement fait avec de nouveaux projets. Cela produit probablement beaucoup d'avertissements dans les projets existants, qui comportent de nombreux tests superflus pour null, de sorte qu'il pourrait être difficile de procéder à une mise à niveau. Mais cela attrapera aussi beaucoup de 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 d'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 arrivé à contrecœur à la conclusion qu'il est acceptable de renvoyer Facultatif à une condition: que la valeur extraite puisse en réalité être nulle. J'ai vu beaucoup de code dans lequel des personnes renvoient régulièrement Optional de getters qui ne peuvent éventuellement renvoyer null. Je considère cela comme une très mauvaise pratique de codage qui ajoute seulement de la complexité au code, ce qui rend les bogues plus probables. Mais lorsque la valeur renvoyée peut en réalité être nulle, continuez et enroulez-la dans un Facultatif.

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

Cela dit, je vois toujours l’approche utilisée par le vérificateur de nullité est le meilleur moyen de traiter les valeurs null, puisqu’elles convertissent des erreurs NullPointerExceptions des bogues d’exécution pour compiler des erreurs de temps.

10
MiguelMunoz

Si vous utilisez des sérialiseurs modernes et d'autres cadres qui comprennent Optional, alors j'ai trouvé ces directives fonctionne bien lors de l'écriture de Entity beans et couches de domaine:

  1. Si la couche de sérialisation (généralement une base de données) autorise une valeur null pour une cellule de la colonne BAR de la table FOO, le getter Foo.getBar() peut renvoyer Optional. indiquant au développeur que cette valeur peut raisonnablement être nulle et qu'il doit le gérer. Si la base de données garantit que la valeur ne sera pas nulle, le getter doit not envelopper ceci dans un Optional.
  2. Foo.bar devrait être private et pas être Optional. Il n'y a vraiment aucune raison pour que ce soit Optional si c'est private.
  3. Le passeur Foo.setBar(String bar) doit prendre le type bar et notOptional. Si vous pouvez utiliser un argument null, indiquez-le dans le commentaire JavaDoc. Si vous ne pouvez pas utiliser null un IllegalArgumentException ou une logique métier appropriée, IMHO est plus approprié.
  4. Les constructeurs n'ont pas besoin d'arguments Optional (pour des raisons similaires au point 3). En règle générale, je n'inclus dans le constructeur que des arguments qui doivent doivent être non nuls dans la base de données de sérialisation.

Pour rendre ce qui précède plus efficace, vous pouvez é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 _ Les générateursIDE gèrent déjà des valeurs nulles).

3
Mark