Perché utilizzare un preferenziale opzionale per il controllo null della variabile?


25

Prendi i due esempi di codice:

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

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

Per quanto ne so, la differenza più ovvia è che l'Opzionale richiede la creazione di un oggetto aggiuntivo.

Tuttavia, molte persone hanno iniziato rapidamente ad adottare Optionals. Qual è il vantaggio dell'utilizzo degli optionals rispetto a un controllo null?


4
non vuoi quasi mai usare isPresent, di solito dovresti usare ifPresent o map
jk.

6
@jk. Perché le ifdichiarazioni sono tremendamente lo scorso decennio e tutti usano astrazioni di monade e lambda ora.
user253751

2
@immibis Una volta ho avuto un lungo dibattito su questo argomento con un altro utente. Il punto è che in if(x.isPresent) fails_on_null(x.get)te esci dal sistema dei tipi e devi mantenere la garanzia che il codice non si spezzerà "nella tua testa" sulla distanza (dichiaratamente breve) tra la condizione e la chiamata di funzione. Nel optional.ifPresent(fails_on_null)sistema dei tipi questa garanzia è per te e non devi preoccuparti.
ziggystar,

1
Il principale difetto di Java nell'uso Optional.ifPresent(e in vari altri costrutti Java) è che puoi solo modificare le variabili finali in modo efficace e non generare eccezioni verificate. Questo è un motivo sufficiente per evitare spesso ifPresent, sfortunatamente.
Mike,

Risposte:


19

Optionalsfrutta il sistema di tipi per fare un lavoro che altrimenti dovresti fare tutto nella tua testa: ricordare se un dato riferimento può essere o meno null. Questo è buono. È sempre intelligente lasciare che il compilatore gestisca lavori noiosi e riservare il pensiero umano a lavori creativi e interessanti.

Senza Optional, ogni riferimento nel tuo codice è come una bomba inesplosa. Accedervi potrebbe fare qualcosa di utile, oppure potrebbe terminare il programma con un'eccezione.

Con Opzionale e senza null, ogni accesso a un riferimento normale ha esito positivo e ogni riferimento a un Opzionale ha esito positivo a meno che non sia disattivato e non sia stato verificato. Questa è una grande vittoria in termini di manutenibilità.

Sfortunatamente, la maggior parte delle lingue che ora offrono Optionalnon è stata abolita null, quindi puoi trarre profitto dal concetto istituendo una rigorosa politica di "assolutamente no null, mai". Pertanto, Optionalad esempio, Java non è così convincente come dovrebbe idealmente essere.


Poiché la tua risposta è la migliore, potrebbe valere la pena aggiungere l'utilizzo di Lambdas come vantaggio - per chiunque legga questo in futuro (vedi la mia risposta)
William Dunne,

Le lingue più moderne forniscono tipi non annullabili, Kotlin, Typescript, AFAIK.
Zen,

5
IMO questo è meglio gestito con un'annotazione @Nullable. Nessun motivo da usare Optionalsolo per ricordarti che il valore potrebbe essere nullo
Hilikus

18

Un Optionalporta una digitazione più forte in operazioni che potrebbero non riuscire, come hanno coperto le altre risposte, ma che è ben lungi dall'essere la cosa più interessante o preziosa che Optionalsporti al tavolo. Molto più utile è la capacità di ritardare o evitare il controllo degli errori e di comporre facilmente molte operazioni che potrebbero non riuscire.

Considera se avevi la tua optionalvariabile dal tuo codice di esempio, quindi dovevi eseguire due passaggi aggiuntivi che ciascuno potrebbe potenzialmente fallire. Se un passaggio lungo la strada non riesce, si desidera invece restituire un valore predefinito. Usando Optionalscorrettamente, si finisce con qualcosa del genere:

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

Con nullavrei dovuto controllare tre volte nullprima di procedere, il che aggiunge molta complessità e problemi di manutenzione al codice. Optionalsavere quel controllo integrato in flatMapeorElse funzioni .

Nota che non ho chiamato isPresentuna volta, che dovresti considerare un odore di codice durante l'utilizzo Optionals. Ciò non significa necessariamente che non dovresti mai usare isPresent, solo che dovresti scrutare attentamente qualsiasi codice che lo fa, per vedere se c'è un modo migliore. Altrimenti, hai ragione, stai solo ottenendo un vantaggio marginale sulla sicurezza rispetto all'uso null.

Nota anche che non sono così preoccupato di incapsulare tutto in un'unica funzione, al fine di proteggere altre parti del mio codice da puntatori null da risultati intermedi. Se ha più senso avere la mia .orElse(defaultValue)in un'altra funzione, ad esempio, ho molti meno scrupoli nel metterlo lì, ed è molto più facile comporre le operazioni tra funzioni diverse secondo necessità.


10

Sottolinea la possibilità di null come risposta valida, che le persone spesso presumono (a ragione o in torto) non verrà restituita. L'evidenziazione delle poche volte in cui è valido null consente di omettere i rifiuti del codice con un numero enorme di controlli null inutili.

Idealmente l'impostazione di una variabile su null sarebbe un errore di compilazione ovunque ma in un facoltativo; eliminando le eccezioni del puntatore null di runtime. Ovviamente la retrocompatibilità lo preclude, purtroppo


4

Lasciate che vi faccia un esempio:

class UserRepository {
     User findById(long id);
}

È abbastanza chiaro a cosa serve questo metodo. Ma cosa succede se il repository non contiene l'utente con ID specificato? Potrebbe generare un'eccezione o forse restituisce null?

Secondo esempio:

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

Ora, cosa succede qui se non ci sono utenti con ID specificato? Bene, ottieni l'istanza Optional.absent () e puoi controllarla con Optional.isPresent (). La firma del metodo con Opzionale è più esplicita sul suo contratto. È immediatamente chiaro come dovrebbe comportarsi il metodo.

Ha anche un vantaggio nella lettura del codice client:

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

Ho allenato i miei occhi a vedere .get () come un odore di codice immediato. Significa che il client ignora intenzionalmente il contratto del metodo invocato. È molto più facile vederlo che senza Opzionale, perché in quel caso non si è immediatamente consapevoli di quale sia il contratto del metodo chiamato (sia che consenta null o meno nel suo ritorno), quindi è necessario controllarlo prima.

Facoltativo viene utilizzato con la convenzione che i metodi con tipo restituito Facoltativo non restituiscono mai null e di solito anche con la convenzione (meno forte) che il metodo senza Tipo restituito facoltativo non restituisce mai null (ma è meglio annotarlo con @Nonnull, @NotNull o simile) .


3

Un Optional<T>ti permette di avere "fallimento" o "nessun risultato" come valore di risposta / ritorno valido per i tuoi metodi (pensa, ad esempio, a una ricerca nel database). Usare un Optionalinvece di usare nullper indicare un errore / nessun risultato ha alcuni vantaggi:

  • Comunica chiaramente che "fallimento" è un'opzione. L'utente del metodo non deve indovinare se nullpotrebbe essere restituito.
  • Il valore nulldovrebbe, almeno a mio parere, non essere usato per indicare nulla come la semantica potrebbero non essere del tutto chiaro. Dovrebbe essere usato per dire al garbage collector che l'oggetto precedentemente riferito potrebbe essere raccolto.
  • La Optionalclasse fornisce molti metodi utili per lavorare in modo condizionale con i valori (ad es. ifPresent()) O definire i valori predefiniti in modo trasparente ( orElse()).

Sfortunatamente, non rimuove completamente la necessità di nullcontrolli nel codice come Optionalpotrebbe essere l' oggetto stesso null.


4
Questo non è davvero ciò che è opzionale. Per segnalare un errore, utilizzare un'eccezione. Se non è possibile farlo, usare un tipo che contiene sia un risultato o un codice di errore .
James Youngman,

Sono fortemente in disaccordo. Opzionale.empty () è un ottimo modo per mostrare fallimento o inesistenza secondo me.
LegendLength

@LegendLength, devo essere fortemente in disaccordo in caso di fallimento. Se una funzione fallisce, è meglio che mi dia una spiegazione, non solo un vuoto, "Ho fallito"
Winston Ewert,

Spiacente, sono d'accordo, intendo "fallimento atteso" come nel non trovare un dipendente con il nome "x" durante una ricerca.
LegendLength

3

Oltre alle altre risposte, l'altro grande vantaggio di Optionals quando si tratta di scrivere codice pulito è che puoi usare un'espressione Lambda nel caso in cui il valore sia presente, come mostrato di seguito:

opTr.ifPresent(tr -> transactions.put(tr.getTxid(), tr));

2

Considera il seguente metodo:

void dance(Person partner)

Ora diamo un'occhiata al codice chiamante:

dance(null);

Ciò causa un arresto potenzialmente difficile da tracciare altrove, poiché dancenon si aspettava un valore nullo.

dance(optionalPerson)

Questo provoca un errore di compilazione, perché la danza si aspettava un Personnon unOptional<Person>

dance(optionalPerson.get())

Se non avessi una persona, ciò causerebbe un'eccezione su questa linea, nel punto in cui ho tentato illegittimamente di convertire la Optional<Person>persona in persona. La chiave è che, a differenza del passaggio di un null, l'eccezione viene rintracciata qui, non in qualche altra posizione nel programma.

Per mettere tutto insieme: l'uso improprio opzionale rende più facile rintracciare i problemi, l'uso improprio di null causa problemi difficili da rintracciare.


1
Se stai generando eccezioni quando la chiamata del Optional.get()tuo codice è scritta male, non dovresti quasi mai chiamare Optional.get senza Optional.isPresent()prima controllare (come indicato nell'OP) o potresti scrivere optionalPersion.ifPresent(myObj::dance).
Chris Cooper,

@QmunkE, sì, dovresti chiamare Optional.get () se sai già che optional non è vuoto (o perché hai chiamato isPresent o perché l'algoritmo lo garantisce). Tuttavia, mi preoccupo che le persone controllino ciecamente IsPresent e quindi ignorino i valori assenti creando una serie di guasti silenziosi che sarà un dolore da rintracciare.
Winston Ewert,

1
Mi piacciono gli optionals. Ma dirò che i semplici puntatori null vecchi sono molto raramente un problema nel codice del mondo reale. Quasi sempre falliscono vicino alla linea di codice offensiva. E penso che venga esagerato dalle persone quando parlano di questo argomento.
LegendLength

@LegendLength, la mia esperienza è che il 95% dei casi è effettivamente facilmente rintracciabile per identificare da dove proviene il NULL. Il restante 5%, tuttavia, è estremamente difficile da rintracciare.
Winston Ewert,

-1

In Objective-C, tutti i riferimenti agli oggetti sono facoltativi. Per questo motivo, il codice come quello che hai postato è stupido.

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

Codice come il precedente è inaudito in Objective-C. Invece, il programmatore avrebbe semplicemente:

[variable doThing];

Se variablecontiene un valore, doThingverrà chiamato su di esso. In caso contrario, nessun danno. Il programma lo tratterà come no-op e continuerà a funzionare.

Questo è il vero vantaggio degli optionals. Non è necessario sporcare il codice con if (obj != nil).


Non è forse solo una forma di fallimento silenzioso? In alcuni casi potrebbe interessarti che il valore sia nullo e desideri fare qualcosa al riguardo.
LegendLength

-3

if (optional.isPresent ()) {

Il problema ovvio qui è che se "facoltativo" è davvero mancante (cioè è nullo), il tuo codice verrà fatto esplodere con una NullReferenceException (o simile) cercando di chiamare qualsiasi metodo su un riferimento a oggetto null!

Potresti voler scrivere un metodo statico di classe Helper che determina la nullità di un determinato tipo di oggetto, qualcosa del genere:

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

if ( isNull( optional ) ) ... 

o, forse, più utilmente:

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

if ( isNotNull( optional ) ) ... 

Ma uno di questi è davvero più leggibile / comprensibile / mantenibile rispetto all'originale?

if ( null == optional ) ... 
if ( null != optional ) ... 
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.