it-swarm.it

Il modo migliore per gestire i null in Java?

Ho del codice che non riesce a causa di NullPointerException. Viene chiamato un metodo sull'oggetto in cui l'oggetto non esiste.

Tuttavia, questo mi ha portato a pensare al modo migliore per risolvere questo problema. Devo sempre codificare in modo difensivo per i null in modo che io abbia un codice a prova di futuro per le eccezioni del puntatore null o devo correggere la causa del null in modo che non si verifichi a valle.

Quali sono i tuoi pensieri?

21
Shaun F

Se null è un parametro di input ragionevole per il metodo, correggere il metodo. In caso contrario, correggere il chiamante. "Ragionevole" è un termine flessibile, quindi propongo il seguente test: in che modo il metodo deve gestire un input null? Se trovi più di una possibile risposta, allora null è not un input ragionevole.

45
user281377

Non utilizzare null, utilizzare Opzionale

Come hai sottolineato, uno dei maggiori problemi con null in Java è che può essere utilizzato ovunque, o almeno per tutti i tipi di riferimento.

È impossibile dire che potrebbe essere null e cosa non potrebbe essere.

Java 8 introduce un modello molto migliore: Optional.

Ed esempio da Oracle:

String version = "UNKNOWN";
if(computer != null) {
  Soundcard soundcard = computer.getSoundcard();
  if(soundcard != null) {
    USB usb = soundcard.getUSB();
    if(usb != null) {
      version = usb.getVersion();
    }
  }
}

Se ognuno di questi può restituire o meno un valore positivo, è possibile modificare le API in Optionals:

String name = computer.flatMap(Computer::getSoundcard)
    .flatMap(Soundcard::getUSB)
    .map(USB::getVersion)
    .orElse("UNKNOWN");

Codificando esplicitamente l'opzione nel tipo, le tue interfacce saranno molto migliori e il tuo codice sarà più pulito.

Se non stai usando Java 8, puoi guardare com.google.common.base.Optional in Google Guava.

Una buona spiegazione da parte del team di Guava: https://github.com/google/guava/wiki/UsingAndAvoidingNullExplained

Una spiegazione più generale degli svantaggi di null, con esempi di diverse lingue: https://www.lucidchart.com/techblog/2015/08/31/the-worst-mistake-of-computer-science/


@ Nonnull, @Nullable

Java 8 aggiunge queste annotazioni per aiutare gli strumenti di controllo del codice come gli IDE a rilevare i problemi. Sono abbastanza limitati nella loro efficacia.


Controlla quando ha senso

Non scrivere il 50% del tuo codice controllando null, in particolare se non c'è nulla di ragionevole che il tuo codice possa fare con un valore null.

D'altra parte, se null potrebbe essere usato e significhi qualcosa, assicurati di usarlo.


Alla fine, ovviamente non puoi rimuovere null da Java. Consiglio vivamente di sostituire l'astrazione Optional ogni volta che è possibile e di controllare null quelle altre volte in cui puoi fare qualcosa di ragionevole al riguardo.

21
Paul Draper

Esistono diversi modi per gestirlo, peppare il codice con if (obj != null) {} non è l'ideale, è disordinato, aggiunge rumore durante la lettura del codice in un secondo momento durante il ciclo di manutenzione ed è soggetto a errori, poiché è facile dimenticare per fare questo avvolgimento della piastra della caldaia.

Dipende se si desidera che il codice continui l'esecuzione silenziosa o fallisca. null è un errore o una condizione prevista?.

Cosa è null

In ogni definizione e caso, Null rappresenta l'assoluta mancanza di dati. I valori null nei database rappresentano la mancanza di un valore per quella colonna. un null String non è lo stesso di un vuoto String, un null int non è la stessa cosa di ZERO, in teoria. In pratica "dipende". Vuoto String può fare una buona implementazione Null Object Per la classe String, per Integer dipende dalla logica aziendale.

Le alternative sono:

  1. Il modello Null Object . Crea un'istanza del tuo oggetto che rappresenta lo stato null e inizializza tutti i riferimenti a quel tipo con un riferimento all'implementazione Null. Ciò è utile per oggetti di tipo valore semplice che non hanno molti riferimenti ad altri oggetti che potrebbero anche essere null e si prevede che siano null come stato valido.

  2. Utilizzare strumenti orientati all'aspetto per tessere metodi con un aspetto Null Checker Che impedisce ai parametri di essere nulli. Questo è per i casi in cui null è un errore.

  3. Usa assert() non molto meglio di if (obj != null){} ma meno rumore.

  4. Utilizzare uno strumento di esecuzione del contratto come Contratti per Java . Stesso caso d'uso di qualcosa come AspectJ ma più recente e utilizza Annotazioni invece di file di configurazione esterni. Il meglio di entrambi i lavori di Aspetti e Asserti.

1 è la soluzione ideale quando si sa che i dati in entrata sono null e devono essere sostituiti con un valore predefinito in modo che i consumatori a monte non debbano avere a che fare con tutto il codice di controllo del boilerplate nullo. Il confronto con valori predefiniti noti sarà anche più espressivo.

2, 3 e 4 sono solo generatori di eccezioni alternativi convenienti per sostituire NullPointerException con qualcosa di più informativo, che è sempre e miglioramento.

Alla fine

null in Java è quasi sempre un errore logico. Dovresti sempre cercare di eliminare la causa principale di NullPointerExceptions. Dovresti cercare di non usare null condizioni come logica aziendale. if (x == null) { i = someDefault; } effettua semplicemente l'assegnazione iniziale a quell'istanza di oggetto predefinita.

8
user7519

L'aggiunta di controlli null può rendere problematico il test. Guarda questa grande lezione ...

Leggi la conferenza sui colloqui di Google Tech: "The Clean Code Talks - Don't Look For Things!" ne parla intorno al minuto 24

http://www.youtube.com/watch?v=RlfLCWKxHJ0&list=PL693EFD059797C21E

La programmazione paranoica prevede l'aggiunta di controlli null ovunque. All'inizio sembra una buona idea, ma dal punto di vista dei test, rende difficile gestire i test del tipo di controllo null.

Inoltre, quando si crea una condizione preliminare per l'esistenza di alcuni oggetti come

class House(Door door){

    .. null check here and throw exception if Door is NULL
    this.door = door
}

ti impedisce di creare House poiché lancerai un'eccezione. Supponiamo che i tuoi casi di test stiano creando oggetti finti per testare qualcosa di diverso da Door, beh, non puoi farlo perché è necessario door

Coloro che hanno sofferto dell'inferno della creazione di Mock sono ben consapevoli di questi tipi di fastidi.

In sintesi, la tua suite di test dovrebbe essere abbastanza robusta da testare porte, case, tetti o qualsiasi altra cosa senza essere paranoico. Seriiusly, quanto è difficile aggiungere un test di controllo null per oggetti specifici nei test :)

Dovresti sempre preferire le applicazioni che funzionano perché hai diversi test che dimostrano che funziona, piuttosto che HOPING funziona semplicemente perché hai un sacco di controlli nulli precondizionati ovunque

8
Constantin

tl; dr - È BUONO verificare la presenza di nulls inattesi, ma MALE per un'applicazione cercare di renderli validi.

Dettagli

Chiaramente, ci sono situazioni in cui null è un input o output valido per un metodo e altri in cui non lo è.

Regola n. 1:

Il javadoc per un metodo che consente un parametro null o restituisce un valore null deve documentarlo chiaramente e spiegare cosa significa null.

Regola n. 2:

Un metodo API non deve essere specificato come accettazione o restituzione di null a meno che non vi sia una buona ragione per farlo.

Data una chiara specifica del "contratto" di un metodo nei confronti di nulls, è un errore di programmazione passare o restituire un null dove non dovresti.

Regola n. 3:

Un'applicazione non dovrebbe tentare di "correggere" errori di programmazione.

Se un metodo rileva un null che non dovrebbe essere lì, non dovrebbe tentare di risolvere il problema trasformandolo in qualcos'altro. Questo nasconde il problema al programmatore. Al contrario, dovrebbe consentire all'NPE di verificarsi e causare un errore in modo che il programmatore possa capire qual è la causa principale e risolverlo. Si spera che l'errore venga notato durante il test. Altrimenti, questo dice qualcosa sulla tua metodologia di test.

Regola n. 4:

Laddove possibile, scrivi il tuo codice per rilevare in anticipo errori di programmazione.

Se hai dei bug nel tuo codice che causano molti NPE, la cosa più difficile può essere capire da dove provengono i valori null. Un modo per semplificare la diagnosi è scrivere il codice in modo che null venga rilevato il prima possibile. Spesso puoi farlo insieme ad altri controlli; per esempio.

public setName(String name) {
    // This also detects `null` as an (intended) side-effect
    if (name.length() == 0) {
        throw new IllegalArgumentException("empty name");
    }
}

(Ci sono ovviamente casi in cui le regole 3 e 4 dovrebbero essere temperate dalla realtà. Ad esempio (regola 3), alcuni tipi di applicazioni devono tentare di continuare dopo rilevare probabilmente errori di programmazione e (regola 4) un controllo eccessivo di parametri errati può avere un impatto sulle prestazioni).

4
Stephen C

Consiglierei di fissare il metodo come difensivo. Per esempio:

String go(String s){  
    return s.toString();  
}

Dovrebbe essere più sulla falsariga di questo:

String go(String s){  
    if(s == null){  
       return "";  
    }     
    return s.toString();  
}

Mi rendo conto che questo è assolutamente banale, ma se l'invocatore si aspetta un oggetto, daigli un oggetto predefinito che non causi il passaggio di un null.

3
Woot4Moo

Le seguenti regole generali su null mi hanno aiutato molto finora:

  1. Se i dati provengono al di fuori del tuo controllo, controlla sistematicamente la presenza di valori null e agisci in modo appropriato. Ciò significa o generare un'eccezione che ha senso per la funzione (selezionata o deselezionata, assicurati solo che il nome dell'eccezione ti dica esattamente cosa sta succedendo). Ma non lasciare MAI un valore libero nel tuo sistema che può potenzialmente portare sorprese.

  2. Se Null rientra nel dominio dei valori appropriati per il tuo modello di dati, gestiscilo in modo appropriato.

  3. Quando si restituiscono valori, provare a non restituire valori null quando possibile. Preferisci sempre elenchi vuoti, stringhe vuote, modelli di oggetti null. Mantenere i valori null come valori restituiti quando questa è la migliore rappresentazione possibile dei dati per un determinato caso d'uso.

  4. Probabilmente il più importante di tutti ... Test, test e test di nuovo. Quando provi il tuo codice non testarlo come programmatore testalo come un dominatore psicopatico nazista e cerca di immaginare tutti i modi per torturare l'inferno fuori da quel codice.

Questo tende un po 'sul lato paranoico per quanto riguarda i null che spesso portano a facciate e proxy che interfacciano i sistemi con il mondo esterno e controllano rigorosamente i valori all'interno con abbondante ridondanza. Il mondo esterno qui significa praticamente tutto ciò che non ho codificato. Ha un costo runtime ma finora raramente ho dovuto ottimizzarlo creando "sezioni null null" di codice. Devo dire però che per lo più creo sistemi di lunga durata per l'assistenza sanitaria e l'ultima cosa che voglio è il sottosistema di interfaccia che trasporta le tue allergie allo iodio allo scanner CT si blocca a causa di un puntatore nullo inaspettato perché qualcun altro in un altro sistema non ha mai capito che i nomi potevano contiene apostrofi o personaggi come 但 耒耨。

comunque .... i miei 2 centesimi

2
Newtopian

Proporrei di usare il modello Option/Some/None dai linguaggi funzionali. Non sono uno specialista Java, ma uso intensamente la mia implementazione di questo modello nel mio progetto C # e sono sicuro che potrebbe essere convertito in Java mondo.

L'idea alla base di questo modello è: se è logico avere una situazione in cui è possibile l'assenza di un valore (ad esempio quando si recupera dal database in base all'id) si fornisce un oggetto di tipo Opzione [T], dove T è un valore possibile . I caso di assenza di un valore oggetto di classe None [T] restituito, nel caso in cui esista il valore - oggetto di Some [T] restituito, contenente il valore.

In questo caso, è necessario gestire la possibilità di assenza di valore e se si esegue la revisione del codice è possibile trovare facilmente placec di gestione errata. Per trarre ispirazione dall'implementazione del linguaggio C #, consultare il mio repository bitbucket https://bitbucket.org/mikegirkin/optionsomenone

Se si restituisce un valore null ed è logicamente equivalente a un errore (ad esempio non esiste alcun file o non è stato possibile connettersi) è necessario generare un'eccezione o utilizzare un altro modello di gestione degli errori. L'idea alla base di questo, ancora una volta, si finisce con una soluzione, quando devi gestire la situazione di assenza di valore e potresti facilmente trovare i luoghi di gestione errata nel codice.

1
Hedin
  1. Non utilizzare il tipo Opzionale, a meno che non sia veramente facoltativo, spesso l'uscita è gestita meglio come un'eccezione, a meno che non ti aspettassi davvero null come opzione e non perché scrivi regolarmente codice errato.

  2. Il problema non è nullo come tipo ha i suoi usi, come sottolinea l'articolo di Google. Il problema è che i null devono essere controllati e gestiti, spesso possono essere usciti con grazia.

  3. Esistono numerosi casi nulli che rappresentano condizioni non valide in aree al di fuori del normale funzionamento del programma (input utente non valido, problemi di database, errori di rete, file mancanti, dati corrotti), che è ciò a cui vengono controllate le eccezioni, gestirle, anche se è solo per registrarlo.

  4. La gestione delle eccezioni è consentita diverse priorità e privilegi operativi nella JVM rispetto a un tipo, come Opzionale, che ha senso per la natura eccezionale della loro occorrenza, incluso il supporto anticipato per il caricamento lento prioritario del gestore nella memoria, poiché non ne ho bisogno in giro tutto il tempo.

  5. Non è necessario scrivere gestori null in tutto il luogo, solo dove è probabile che si verifichino, ovunque si acceda potenzialmente a un servizio dati inaffidabile, e poiché la maggior parte di questi rientra in alcuni schemi generali che possono essere facilmente astratti, è sufficiente una chiamata del gestore, tranne in rari casi.

Quindi immagino che la mia risposta sarebbe racchiuderla in un'eccezione controllata e gestirla, o correggere il codice inaffidabile se è nelle tue capacità.

0
J-Boss

Bene, se uno dei possibili risultati del tuo metodo è un valore nullo, allora dovresti codificarlo in modo difensivo, ma se un metodo dovrebbe restituire un valore non nullo, ma non lo risolverei di sicuro.

Come al solito dipende dal caso, come nella maggior parte delle cose nella vita :)

0
jonezy

Le librerie lang di Apache Commons forniscono un modo per gestire valori nulli

il metodo defaultIfNull nella classe ObjectUtils consente di restituire un valore predefinito se l'oggetto passato è nullo

0
Mahmoud Hossam