Pourquoi utilise-t-on une préférence facultative pour vérifier la variable nulle?

25

Prenez les deux exemples de code:

if(optional.isPresent()) {
    //do your thing
}

if(variable != null) {
    //do your thing
}

Autant que je sache, la différence la plus évidente est que l'option Facultatif nécessite la création d'un objet supplémentaire.

Cependant, de nombreuses personnes ont commencé à adopter rapidement les options. Quel est l'avantage d'utiliser des options par rapport à une vérification nulle?

William Dunne
la source
4
vous ne voulez presque jamais utiliser isPresent, vous devriez généralement utiliser ifPresent ou map
jk.
6
@jk. Parce que les ifdéclarations sont tellement la dernière décennie, et tout le monde utilise des abstractions de monade et des lambdas maintenant.
user253751
2
@immibis J'ai eu un long débat sur ce sujet avec un autre utilisateur. Le fait est que if(x.isPresent) fails_on_null(x.get)vous quittez le système de type et devez garder la garantie que le code ne se cassera pas "dans votre tête" sur la distance (certes courte) entre la condition et l'appel de fonction. Dans optional.ifPresent(fails_on_null)le système de type, cette garantie est faite pour vous, et vous n'avez pas à vous inquiéter.
ziggystar
1
Le principal défaut de Java avec l'utilisation Optional.ifPresent(et diverses autres constructions Java) est que vous ne pouvez modifier efficacement que les variables finales et ne pouvez pas lever d'exceptions vérifiées. C'est une raison suffisante pour éviter souvent ifPresent, malheureusement.
Mike

Réponses:

19

Optionalexploite le système de typage pour effectuer un travail que vous auriez autrement dû faire tout dans votre tête: se rappeler si une référence donnée peut être ou non null. C'est bon. Il est toujours intelligent de laisser le compilateur gérer des travaux ennuyeux et de réserver la pensée humaine à un travail créatif et intéressant.

Sans Optional, chaque référence dans votre code est comme une bombe non explosée. L'accès peut faire quelque chose d'utile, ou bien il peut mettre fin à votre programme avec une exception.

Avec Facultatif et sans null, chaque accès à une référence normale réussit, et chaque référence à une Option réussit à moins qu'il ne soit pas défini et que vous n'ayez pas vérifié cela. C'est une énorme victoire en maintenabilité.

Malheureusement, la plupart des langues actuellement proposées Optionaln'ont pas été abolies null, vous ne pouvez donc que profiter du concept en instituant une politique stricte de «absolument non null, jamais». Par conséquent, Optionalpar exemple, Java n'est pas aussi convaincant qu'il devrait idéalement l'être.

Kilian Foth
la source
Étant donné que votre réponse est la meilleure, il pourrait être utile d'ajouter l'utilisation de Lambdas comme un avantage - pour quiconque lira ceci à l'avenir (voir ma réponse)
William Dunne
La plupart des langages modernes proposent des types non nullables, Kotlin, Typescript, AFAIK.
Zen
5
OMI, cela est mieux géré avec une annotation @Nullable. Aucune raison d'utiliser Optionaljuste pour vous rappeler que la valeur pourrait être nulle
Hilikus
18

Un Optionalapporte un typage plus fort dans les opérations qui peuvent échouer, comme les autres réponses l'ont couvert, mais c'est loin d'être la chose la plus intéressante ou la plus précieuse à Optionalsapporter. Beaucoup plus utile est la possibilité de retarder ou d'éviter la vérification d'échec, et de composer facilement de nombreuses opérations qui peuvent échouer.

Considérez si vous aviez votre optionalvariable de votre exemple de code, alors vous avez dû effectuer deux étapes supplémentaires qui pourraient potentiellement échouer chacune. Si une étape échoue, vous souhaitez renvoyer une valeur par défaut à la place. En utilisant Optionalscorrectement, vous vous retrouvez avec quelque chose comme ceci:

return optional.flatMap(x -> x.anotherOptionalStep())
               .flatMap(x -> x.yetAnotherOptionalStep())
               .orElse(defaultValue);

Avec, nullj'aurais dû vérifier trois fois nullavant de continuer, ce qui ajoute beaucoup de complexité et de maux de tête de maintenance au code. Optionalsavoir cette vérification intégrée aux fonctions flatMapet orElse.

Notez que je n'ai pas appelé isPresentune seule fois, ce que vous devriez considérer comme une odeur de code lors de l'utilisation Optionals. Cela ne signifie pas nécessairement que vous ne devriez jamais utiliser isPresent, juste que vous devriez examiner attentivement tout code qui le fait, pour voir s'il existe une meilleure façon. Sinon, vous avez raison, vous n'obtenez qu'un avantage de sécurité de type marginal sur l'utilisation null.

Notez également que je ne suis pas aussi inquiet à l'idée d'encapsuler tout cela dans une seule fonction, afin de protéger les autres parties de mon code contre les pointeurs nuls des résultats intermédiaires. S'il est plus logique d'avoir mon .orElse(defaultValue)dans une autre fonction par exemple, j'ai beaucoup moins de scrupules à le mettre là-bas, et il est beaucoup plus facile de composer les opérations entre différentes fonctions selon les besoins.

Karl Bielefeldt
la source
10

Il met en évidence la possibilité de null comme réponse valide, ce que les gens supposent souvent (à tort ou à raison) ne sera pas retourné. Mettre en évidence les quelques fois où null est valide permet d'omettre de jeter votre code avec un grand nombre de vérifications nulles inutiles.

Idéalement, la définition d'une variable sur null serait une erreur de compilation n'importe où, mais en option; éliminer les exceptions de pointeur nul à l'exécution. De toute évidence, la rétrocompatibilité empêche cela, malheureusement

Richard Tingle
la source
4

Laisse moi te donner un exemple:

class UserRepository {
     User findById(long id);
}

Il est assez clair à quoi cette méthode est destinée. Mais que se passe-t-il si le référentiel ne contient pas d'utilisateur avec un identifiant donné? Il pourrait lever une exception ou peut-être qu'il renvoie null?

Deuxième exemple:

class UserRepository {
     Optional<User> findById(long id);
}

Maintenant, que se passe-t-il ici s'il n'y a pas d'utilisateur avec un identifiant donné? À droite, vous obtenez une instance Optional.absent () et vous pouvez la vérifier avec Optional.isPresent (). La signature de méthode avec Facultatif est plus explicite sur son contrat. Il est immédiatement clair, comment la méthode est censée se comporter.

Il présente également un avantage lors de la lecture du code client:

User user = userRepository.findById(userId).get();

J'ai formé mon œil pour voir .get () comme une odeur de code immédiate. Cela signifie que le client ignore délibérément le contrat de la méthode invoquée. Il est beaucoup plus facile de voir cela que sans optionnel, car dans ce cas, vous ne savez pas immédiatement quel est le contrat de la méthode appelée (si elle autorise null ou non dans son retour), vous devez donc le vérifier d'abord.

Facultatif est utilisé avec la convention selon laquelle les méthodes avec le type de retour Facultatif ne retournent jamais null et généralement aussi avec la convention (moins forte) cette méthode sans le type de retour facultatif ne renvoie jamais null (mais il est préférable de l'annoter avec @Nonnull, @NotNull ou similaire) .

qbd
la source
3

Un Optional<T>vous permet d'avoir "échec" ou "aucun résultat" comme valeur de réponse / retour valide pour vos méthodes (pensez, par exemple, à une recherche de base de données). L'utilisation d'un Optionalau lieu d'utiliser nullpour indiquer un échec / aucun résultat présente certains avantages:

  • Il indique clairement que «l'échec» est une option. L'utilisateur de votre méthode n'a pas à deviner s'il nullpeut être retourné.
  • La valeur nulldevrait, au moins à mon avis, pas être utilisé pour indiquer quoi que ce soit la sémantique pourrait ne pas être tout à fait clair. Il doit être utilisé pour indiquer au ramasse-miettes que l'objet mentionné précédemment peut être collecté.
  • La Optionalclasse fournit de nombreuses méthodes intéressantes pour travailler conditionnellement avec les valeurs (par exemple, ifPresent()) ou définir les valeurs par défaut de manière transparente ( orElse()).

Malheureusement, cela ne supprime pas complètement la nécessité de nullvérifier le code, car l' Optionalobjet lui-même pourrait toujours l'être null.

pansen
la source
4
Ce n'est pas vraiment ce à quoi Optionnel est destiné. Pour signaler une erreur, utilisez une exception. Si vous ne pouvez pas faire cela, utilisez un type qui contient soit un résultat soit un code d'erreur .
James Youngman
Je suis fortement en désaccord. Optional.empty () est un excellent moyen de montrer l'échec ou la non-existence à mon avis.
LegendLength
@LegendLength, je dois être fortement en désaccord en cas d'échec. Si une fonction échoue, il vaut mieux me donner une explication, pas seulement un vide, "j'ai échoué"
Winston Ewert
Désolé, je suis d'accord, je veux dire «échec attendu» car ne pas trouver un employé avec le nom «x» lors d'une recherche.
LegendLength
3

En plus des autres réponses, l'autre avantage majeur des options quand il s'agit d'écrire du code propre est que vous pouvez utiliser une expression Lambda dans le cas où la valeur est présente, comme indiqué ci-dessous:

opTr.ifPresent(tr -> transactions.put(tr.getTxid(), tr));
William Dunne
la source
2

Considérez la méthode suivante:

void dance(Person partner)

Voyons maintenant un code d'appel:

dance(null);

Cela provoque un accident potentiellement difficile à suivre ailleurs, car dance ne s'attendait pas à une valeur nulle.

dance(optionalPerson)

Cela provoque une erreur de compilation, car la danse attendait un Personpas unOptional<Person>

dance(optionalPerson.get())

Si je n'avais pas de personne, cela provoque une exception sur cette ligne, au point où j'ai tenté de convertir le Optional<Person>en personne. La clé est que, contrairement au passage d'une valeur nulle, les traces exceptées ici, pas un autre emplacement dans le programme.

Pour mettre tout cela ensemble: une mauvaise utilisation en option produit plus facilement des problèmes de recherche, une mauvaise utilisation de null entraîne des problèmes difficiles à localiser.

Winston Ewert
la source
1
Si vous lancez des exceptions lors de l'appel de Optional.get()votre code est mal écrit - vous ne devriez presque jamais appeler Optional.get sans vérifier d' Optional.isPresent()abord (comme indiqué dans l'OP) ou vous pourriez écrire optionalPersion.ifPresent(myObj::dance).
Chris Cooper
@QmunkE, oui, vous ne devez appeler Optional.get () que si vous savez déjà que facultatif n'est pas vide (soit parce que vous avez appelé isPresent, soit parce que votre algorithme le garantit). Cependant, je m'inquiète que les gens vérifient aveuglément isPresent et ignorent ensuite les valeurs absentes créant une multitude d'échecs silencieux qui seront difficiles à retracer.
Winston Ewert
1
J'aime les options. Mais je dirai que les anciens pointeurs nus sont très rarement un problème dans le code du monde réel. Ils échouent presque toujours près de la ligne de code incriminée. Et je pense que les gens exagèrent lorsqu'ils parlent de ce sujet.
LegendLength
@LegendLength, mon expérience est que 95% des cas sont en effet facilement traçables pour identifier d'où vient le NULL. Les 5% restants sont cependant extrêmement difficiles à retrouver.
Winston Ewert du
-1

Dans Objective-C, toutes les références d'objet sont facultatives. Pour cette raison, un code comme celui que vous avez publié est stupide.

if (variable != nil) {
    [variable doThing];
}

Un code comme celui-ci est inconnu dans Objective-C. Au lieu de cela, le programmeur devrait simplement:

[variable doThing];

Si variablecontient une valeur, alors doThingsera appelé dessus. Sinon, pas de mal. Le programme le traitera comme un no-op et continuera à fonctionner.

C'est le véritable avantage des options. Vous n'avez pas besoin de salir votre code if (obj != nil).

Daniel T.
la source
N'est-ce pas juste une forme d'échec silencieux? Dans certains cas, vous pouvez vous soucier que la valeur est nulle et vouloir faire quelque chose.
LegendLength
-3

if (optional.isPresent ()) {

Le problème évident ici est que si "facultatif" vraiment est manquant (c'est-à-dire est nul) alors votre code va exploser avec une NullReferenceException (ou similaire) essayant d'appeler n'importe quelle méthode sur une référence d'objet null!

Vous voudrez peut-être écrire une méthode statique de classe Helper qui détermine la nullité de tout type d'objet donné, quelque chose comme ceci:

function isNull( object o ) 
{
   return ( null == o ); 
}

if ( isNull( optional ) ) ... 

ou, peut-être, plus utilement:

function isNotNull( object o ) 
{
   return ( null != o ); 
}

if ( isNotNull( optional ) ) ... 

Mais l'un ou l'autre de ces éléments est-il vraiment plus lisible / compréhensible / maintenable que l'original?

if ( null == optional ) ... 
if ( null != optional ) ... 
Phill W.
la source