Il modo migliore per gestire i null in Java? [chiuso]


21

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 dovrei correggere la causa del null in modo che non si verifichi a valle.

Quali sono i tuoi pensieri?


5
Perché non dovresti "correggere la causa del null"? Puoi fornire un esempio del perché questa non è l'unica scelta sensata? Chiaramente, qualcosa deve allontanarti dall'ovvio. Che cos'è? Perché la correzione non è la causa principale della tua prima scelta?
S. Lott,

Risolvere bene la "causa principale" del problema può essere d'aiuto a breve termine, ma se il codice non sta ancora controllando in modo difensivo i valori null e viene riutilizzato da un altro processo che potrebbe introdurre un valore null, dovrei risolverlo di nuovo.
Shaun F,

2
@Shaun F: Se il codice è rotto, è rotto. Non capisco come sia possibile che il codice produca null imprevisti e non venga corretto. Chiaramente, c'è qualcosa di "stupido" o "politico" in corso. O qualcosa che consente al codice errato di essere in qualche modo accettabile. Puoi spiegare come il codice errato non viene corretto? Qual è il background politico che ti porta a porre questa domanda?
S. Lott,

1
"vulnerabile alla successiva creazione di dati che conteneva valori null" Cosa significa? Se "Riesco a correggere la routine di creazione dei dati", non c'è ulteriore vulnerabilità. Non riesco a capire cosa possa significare questa "successiva creazione di dati che contiene valori null"? Software difettoso?
S. Lott,

1
@ S.Lott, incapsulamento e singola responsabilità. Tutto il tuo codice non "sa" cosa fa tutto il tuo altro codice. Se una classe accetta Froobinator, fa il minor numero possibile di ipotesi su chi ha creato il Froobinator e su come. Proviene da un codice "esterno" o proviene da una parte diversa della base di codice. Non sto dicendo che non dovresti correggere il codice buggy; ma cerca "programmazione difensiva" e troverai a cosa si riferisce l'OP.
Paul Draper,

Risposte:


45

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 non è un input ragionevole.


1
È davvero così semplice.
biziclop,

3
Guava ha alcuni simpatici metodi di supporto che rendono il controllo null semplice come Precondition.checkNotNull(...). Vedere stackoverflow.com/questions/3022319/...

La mia regola è di inizializzare tutto su valori predefiniti sensibili, se possibile, e preoccuparmi di eccezioni nulle in seguito.
davidk01,

Thorbjørn: Quindi sostituisce una NullPointerException accidentale con una NullPointerException intenzionale? In alcuni casi, eseguire il controllo anticipato potrebbe essere utile o addirittura necessario; ma temo un sacco di assegni nulli senza senso sulla caldaia che aggiungono poco valore e rendono il programma più grande.
user281377

6
Se null non è un parametro di input ragionevole per il tuo metodo ma è probabile che le chiamate del metodo passino accidentalmente null (specialmente se il metodo è pubblico), potresti anche voler far lanciare il tuo metodo IllegalArgumentExceptionquando viene dato null. Questo segnala ai chiamanti del metodo che il bug è nel loro codice (piuttosto che nel metodo stesso).
Brian,

20

Non utilizzare null, utilizzare Opzionale

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

È impossibile dire che potrebbe essere nulle 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, puoi 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 utilizzando Java 8, puoi consultare com.google.common.base.OptionalGoogle 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 in 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 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 nullvalore.

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


Alla fine, ovviamente, non è possibile rimuovere nullda Java. Consiglio vivamente di sostituire l' Optionalastrazione ogni volta che è possibile e di controllare nullquelle altre volte che puoi fare qualcosa di ragionevole al riguardo.


2
Normalmente, le risposte a domande di quattro anni finiscono per essere eliminate a causa della bassa qualità. Ottimo lavoro nel parlare di come Java 8 (che non esisteva allora) può risolvere il problema.

ma irrilevante per la domanda originale ovviamente. E che dire dell'argomento è davvero facoltativo?
jwenting

5
@jwenting È completamente pertinente alla domanda originale. La risposta a "Qual è il modo migliore per guidare un chiodo con un cacciavite" è "Usa un martello anziché un cacciavite".
Daenyth,

@jwenting, penso di averlo trattato, ma per chiarezza: se l'argomento non è facoltativo, in genere non verificherei nulla. Avrai 100 righe di controllo null e 40 righe di sostanza. Controlla null nelle interfacce più pubbliche o quando null ha effettivamente senso (ovvero l'argomento è facoltativo).
Paul Draper,

4
@ J-Boss, come, in Java, non avresti un deselezionato NullPointerException? A NullPointerExceptionpuò succedere letteralmente ogni volta che chiami un metodo di istanza in Java. Avresti avuto throws NullPointerExceptionin quasi ogni singolo metodo di sempre.
Paul Draper,

8

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 di avvolgere questa piastra della caldaia.

Dipende se si desidera che il codice continui l'esecuzione silenziosa o non riesca. È nullun 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 Stringnon è lo stesso di un vuoto String, un null intnon è la stessa cosa di ZERO, in teoria. In pratica "dipende". Vuoto Stringpuò essere una buona Null Objectimplementazione per la classe String, poiché Integerdipende dalla logica aziendale.

Le alternative sono:

  1. Il Null Objectmodello. Creare un'istanza dell'oggetto che rappresenta lo nullstato e inizializzare tutti i riferimenti a quel tipo con un riferimento Nullall'implementazione. Ciò è utile per oggetti di tipo valore semplice che non hanno molti riferimenti ad altri oggetti che potrebbero anche essere nulle si prevede che siano nulluno stato valido.

  2. Utilizzare strumenti orientati all'aspetto per tessere metodi con un Null Checkeraspetto che impedisce ai parametri di essere nulli. Questo è per i casi in cui nullsi verifica un errore.

  3. Usa assert()non molto meglio del 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 entrambe le opere di Aspetti e Asserti.

1 è la soluzione ideale quando si sa che i dati in entrata sono nulle 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 della caldaia a null. Il confronto con valori predefiniti noti sarà anche più espressivo.

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

Alla fine

nullin Java è quasi sempre un errore logico. Dovresti sempre cercare di eliminare la causa principale di NullPointerExceptions. Dovresti cercare di non usare le nullcondizioni come logica di business. if (x == null) { i = someDefault; }basta effettuare l'assegnazione iniziale a quell'istanza dell'oggetto predefinita.


Se possibile, il modello a oggetti null è la soluzione, è il modo più elegante per gestire un caso null. È raro che tu voglia un null per far esplodere le cose in produzione!
Richard Miskin,

@Richard: a meno che ciò non nullsia inatteso, si tratta di un vero errore, quindi tutto dovrebbe arrestarsi completamente.

Nulla nei database e nel codice di programmazione viene gestito in modo diverso in un aspetto importante: nella programmazione null == null(anche in PHP), ma nei database, null != nullperché nei database rappresenta un valore sconosciuto anziché "nulla". Due incognite non sono necessariamente uguali, mentre due nulla sono uguali.
Simon Forsberg,

8

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 SPERANZA funziona semplicemente perché hai un sacco di controlli null precondizionati ovunque


4

tl; dr - è BUONO controllare se ci sono messaggi inattesi nullma BAD per un'applicazione che cerca di renderli validi.

Dettagli

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

Regola n. 1:

Il javadoc per un metodo che consente un nullparametro o restituisce un nullvalore deve documentarlo chiaramente e spiegarne il nullsignificato.

Regola n. 2:

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

Data una chiara specifica del "contratto" di un metodo nei confronti di un utente null, è un errore di programmazione passare o restituire un punto in nullcui non si dovrebbe.

Regola n. 3:

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

Se un metodo rileva un elemento nullche non dovrebbe essere presente, non dovrebbe tentare di risolvere il problema trasformandolo in qualcos'altro. Questo nasconde il problema al programmatore. Invece, 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:

Ove 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 nullprovengono i valori. Un modo per semplificare la diagnosi è scrivere il codice in modo che nullvenga 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 mitigate dalla realtà. Ad esempio (regola 3), alcuni tipi di applicazioni devono tentare di continuare dopo aver rilevato probabilmente errori di programmazione. E (regola 4) un eccessivo controllo di parametri errati può avere un impatto sulle prestazioni.)


3

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, dagli un oggetto predefinito che non causi il passaggio di un null.


10
Ciò ha un brutto effetto collaterale in quanto il chiamante (programmatore che codifica contro questa API) potrebbe sviluppare l'abitudine di passare null come parametro per ottenere un comportamento "predefinito".
Goran Jovic,

2
Se questo è Java, non dovresti usare new String()affatto.
biziclop,

1
@biziclop in realtà è molto più importante di quanto molti credano.
Woot4Moo,

1
Secondo me ha più senso porre un'affermazione e lanciare un'eccezione se l'argomento è nullo, poiché è chiaramente l'errore del chiamante. Non "correggere" silenziosamente gli errori del chiamante; invece, renderli consapevoli di loro.
Andres F.

1
@ Woot4Moo Quindi scrivi il tuo codice che genera un'eccezione. L'importante è informare il chiamante che gli argomenti passati non sono corretti e informarli al più presto. La correzione silenziosa di nulls è l'opzione peggiore possibile, peggio che lanciare un NPE.
Andres F.

2

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. Supporta un runtime di costo, 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


1

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

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 # fare riferimento al 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 è necessario gestire la situazione di assenza di valore e si possono facilmente trovare i luoghi di gestione errata nel codice.


0

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
  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à.

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.