Si dovrebbe controllare null se non si aspetta null?


113

La scorsa settimana abbiamo discusso a fondo della gestione dei null nel livello di servizio della nostra applicazione. La domanda è nel contesto .NET, ma sarà la stessa in Java e in molte altre tecnologie.

La domanda era: dovresti sempre verificare la presenza di valori null e far funzionare il tuo codice in ogni caso, oppure lasciare che un'eccezione compaia quando un null viene ricevuto inaspettatamente?

Da un lato, verificare la presenza di null nel punto in cui non ci si aspetta (ovvero non avere un'interfaccia utente per gestirlo) è, a mio avviso, lo stesso di scrivere un blocco try con catch vuoto. Stai nascondendo un errore. L'errore potrebbe essere che qualcosa è cambiato nel codice e null è ora un valore previsto, oppure c'è qualche altro errore e l'ID errato viene passato al metodo.

D'altra parte, verificare la presenza di null può essere una buona abitudine in generale. Inoltre, se è presente un controllo, l'applicazione potrebbe continuare a funzionare, con solo una piccola parte della funzionalità che non ha alcun effetto. Quindi il cliente potrebbe segnalare un piccolo bug come "impossibile eliminare il commento" invece di un bug molto più grave come "impossibile aprire la pagina X".

Quale pratica segui e quali sono i tuoi argomenti a favore o contro entrambi gli approcci?

Aggiornare:

Voglio aggiungere alcuni dettagli sul nostro caso particolare. Stavamo recuperando alcuni oggetti dal database e ne abbiamo elaborato alcuni (diciamo, costruiamo una raccolta). Lo sviluppatore che ha scritto il codice non ha previsto che l'oggetto potesse essere nullo, quindi non ha incluso alcun controllo e quando la pagina è stata caricata si è verificato un errore e l'intera pagina non è stata caricata.

Ovviamente, in questo caso avrebbe dovuto esserci un controllo. Quindi abbiamo discusso sul fatto che ogni oggetto che viene elaborato debba essere verificato, anche se non è previsto che manchi, e se l'eventuale elaborazione debba essere interrotta silenziosamente.

Il vantaggio ipotetico sarebbe che la pagina continuerà a funzionare. Pensa ai risultati di una ricerca su Stack Exchange in diversi gruppi (utenti, commenti, domande). Il metodo potrebbe controllare null e interrompere l'elaborazione degli utenti (che a causa di un bug è null) ma restituire le sezioni "commenti" e "domande". La pagina continuerà a funzionare tranne per il fatto che la sezione "utenti" mancherà (che è un bug). Dovremmo fallire presto e interrompere l'intera pagina o continuare a lavorare e attendere che qualcuno noti che manca la sezione "utenti"?


62
Principio di Fox Mulder: non fidarti di nessuno.
yannis,

14
@YannisRizos: questo principio è valido - è così positivo che i creatori di Java e C # lasciano che l'ambiente di runtime del linguaggio lo faccia automaticamente. Quindi normalmente non è necessario eseguire controlli espliciti per null ovunque nel codice in quelle lingue.
Doc Brown,


Sono d'accordo con la risposta di tdammers. Credo che controllare null sia semplicemente sbagliato perché non hai un modo ragionevole di gestirlo. Tuttavia, nel tuo scenario, puoi gestire una sezione non riuscita, ad esempio la convalida di un utente (o la ricezione di commenti o qualsiasi altra cosa possa essere) e la presenza di una casella "oh no si è verificato un errore". Registro personalmente tutte le eccezioni nella mia app e filtrare determinate eccezioni come 404s e la connessione si chiude in modo imprevisto

2
assert(foo != null, "foo is web control within the repeater, there's no reason to expect it to be null, etc, etc...");
zzzzBov,

Risposte:


105

La domanda non è tanto se si dovrebbe verificare nullo consentire al runtime di generare un'eccezione; è come dovresti rispondere a una situazione così inaspettata.

Le tue opzioni, quindi, sono:

  • Lancia un'eccezione generica ( NullReferenceException) e lasciala bolle; se non esegui il nullcontrollo da solo, questo è ciò che accade automaticamente.
  • Generare un'eccezione personalizzata che descriva il problema a un livello superiore; ciò può essere ottenuto lanciando il nullsegno di spunta o catturando un NullReferenceExceptione lanciando l'eccezione più specifica.
  • Ripristina sostituendo un valore predefinito adatto.

Non esiste una regola generale quale sia la soluzione migliore. Personalmente, direi:

  • L'eccezione generica è la migliore se la condizione di errore è un segno di un grave errore nel tuo codice, cioè il valore null non dovrebbe mai essere permesso di arrivarci in primo luogo. Un'eccezione del genere può quindi trasformarsi completamente in qualsiasi errore di registrazione che hai impostato, in modo che qualcuno venga avvisato e risolva il bug.
  • La soluzione a valore predefinito è buona se fornire un output semi-utile è più importante della correttezza, come un browser Web che accetta HTML tecnicamente errato e fa il possibile per renderlo sensato.
  • Altrimenti, andrei con l'eccezione specifica e la gestirò in un posto adatto.

1
@Stilgar per l'utente finale non c'è differenza. per lo sviluppatore un'eccezione specifica come "database-server non è raggiungibile" è più intuitiva di "null pointer excepteion"
k3b

32
Fallire in modo duro e precoce significa che c'è meno rischio che cose vadano più sottilmente nella linea, cose come l'autenticazione errata, i dati danneggiati persistono nella memorizzazione, la perdita di informazioni e altre cose divertenti.
Lars Viklund,

1
@Stilgar: ci sono due preoccupazioni qui: comportamento incustodito e manutenibilità. Mentre per il comportamento incustodito c'è poca differenza tra un'eccezione specifica e un'eccezione generica, per manutenibilità può fare la differenza tra "oh, ecco perché le cose esplodono" e un debugathlon di più ore.
tdammers,

3
tdammers ha esattamente ragione. Il "riferimento all'oggetto non impostato su un'istanza di un oggetto" è una delle eccezioni più fastidiose che vedo con più frequenza. Preferirei vedere un'eccezione specifica se si tratta di ArgumentNullException con il nome del parametro o di un'eccezione personalizzata "Non esiste alcun record Post per l'utente TK421".
Sean Chase,

1
@ k3b Anche per l'utente finale, "database-server non è raggiungibile" è molto utile. Almeno, per qualsiasi utente finale che abbia un po 'di indizio sui problemi comuni - potrebbe aiutarlo a scoprire che un cavo è allentato, il router deve essere riavviato, ecc. Piuttosto che arrabbiarsi con il software difettoso.
Julia Hayward,

58

L'IMHO che prova a gestire valori null che non ti aspetti porta a un codice eccessivamente complicato. Se non ti aspetti null, chiariscilo lanciando ArgumentNullException. Mi sento davvero frustrato quando le persone controllano se il valore è null e quindi provo a scrivere del codice che non ha alcun senso. Lo stesso vale per l'utilizzo SingleOrDefault(o anche peggio per ottenere la raccolta) quando qualcuno si aspetta davvero Singlee molti altri casi in cui le persone hanno paura (non so davvero cosa) di dichiarare chiaramente la loro logica.


Invece di ArgumentNullException, si dovrebbero usare i contratti di codice (a meno che non si tratti di un codice sorgente precedente al 2010 che non supporta i contratti di codice).
Arseni Mourzenko,

Sono d'accordo con te. Se il valore nullo non è previsto, non deve essere gestito in modo strano ma segnalato solo.
Giorgio,

9
se ho letto bene la domanda, lanciare un ArgumentNullExceptionconteggio come "gestione" del valore null: impedisce un'eccezione di riferimento null successiva.
KutuluMike,

@MichaelEdenfield: non credo che valga come una vera gestione in base alla domanda data (il nostro obiettivo non è far funzionare il tuo codice, non importa quale ). Ad essere sincero, spesso dimentico di scrivere il blocco appropriato che verrà lanciato se viene passato null. Tuttavia, ritengo che lanciare NullArgumentException sia la migliore pratica e la peggiore pratica secondo me è scrivere codice che non può gestire null ma invece di generare restituzioni di eccezioni, ad esempio un altro null come risultato di un metodo (o stringa vuota, o raccolta vuota, oppure -1 oppure salta l'esecuzione del metodo se il tipo restituito è nullo, ecc.).
empi,

2
@Giorgio, ArgumentNullExceptionchiarisce qual è il problema e come risolverlo. C # non tende ad usare "non definito". Se chiami qualcosa in un modo indefinito, il modo in cui lo chiami non ha senso. Un'eccezione è il modo corretto di indicarlo. Ora, a volte realizzerò metodi di analisi che restituiscono null se int(per esempio) non può essere analizzato. Ma questo è un caso in cui un risultato non analizzabile è una possibilità legittima piuttosto che un errore di utilizzo. Vedi anche questo articolo .
Kyralessa

35

L'abitudine di verificare la nullmia esperienza proviene da ex sviluppatori C o C ++, in quelle lingue hai buone probabilità di nascondere un grave errore quando non lo controlli NULL. Come sapete, in Java o C # le cose sono diverse, l'utilizzo di un riferimento null senza controllo nullprovocherà un'eccezione, quindi l'errore non verrà nascosto segretamente fintanto che non lo ignorerete sul lato chiamante. Quindi, nella maggior parte dei casi, controllare esplicitamente nullnon ha molto senso e complica eccessivamente il codice più del necessario. Ecco perché non considero il controllo null come una buona abitudine in quelle lingue (almeno, non in generale).

Certo, ci sono eccezioni a quella regola, ecco quelle a cui riesco a pensare:

  • vuoi un messaggio di errore migliore, leggibile dall'uomo, che dica all'utente in quale parte del programma si è verificato il riferimento null errato. Quindi il test per null genera solo un'eccezione diversa, con un testo di errore diverso. Ovviamente, nessun errore verrà mascherato in questo modo.

  • vuoi far "fallire più presto" il tuo programma. Ad esempio, si desidera verificare il valore null di un parametro del costruttore, in cui altrimenti il ​​riferimento all'oggetto verrebbe archiviato solo in una variabile membro e utilizzato successivamente.

  • è possibile gestire la situazione di un riferimento null in modo sicuro, senza il rischio di guasti successivi e senza mascherare un errore grave.

  • si desidera un tipo completamente diverso di segnalazione degli errori (ad esempio, la restituzione di un codice di errore) nel contesto corrente


3
"vuoi un messaggio di errore migliore e leggibile dall'uomo" - questo è il miglior motivo per farlo, secondo me. "Il riferimento oggetto non impostato su un'istanza di un oggetto" o "Eccezione riferimento null" non è mai utile come "Prodotto non istanziato".
pdr,

@pdr: un altro motivo per verificare è assicurarsi che sia sempre rilevato.
Giorgio,

13
"Ex sviluppatori C", forse. "Ex sviluppatori C ++" solo se quelli stessi stanno recuperando sviluppatori C. Come moderno sviluppatore C ++, sarei molto sospettoso riguardo al codice che utilizza puntatori nudi o allocazione dinamica spericolata. In effetti, sarei tentato di ribaltare l'argomento e dire che C ++ non presenta il problema descritto dall'OP perché le sue variabili non hanno la speciale proprietà di nullità aggiuntiva di Java e C #.
Kerrek SB,

7
Il tuo primo paragrafo è solo metà della storia: sì, se usi un riferimento null in C #, otterrai un'eccezione, mentre in C ++ otterrai un comportamento indefinito. Ma nel C # ben scritto, sei bloccato con riferimenti molto più nulli che nel C ++ ben scritto.
James McNellis,

Migliore è l'incapsulamento, migliore è questa risposta.
radarbob,

15

Usa assert per testare e indicare pre / postcondizioni e invarianti per il tuo codice.

Rende molto più facile capire cosa si aspetta il codice e cosa ci si può aspettare da gestire.

Un'asserzione, IMO, è un controllo adeguato perché è:

  • efficiente (di solito non utilizzato in versione),
  • breve (di solito una sola riga) e
  • cancella (condizione preliminare violata, si tratta di un errore di programmazione).

Penso che il codice di auto-documentazione sia importante, quindi avere un controllo è buono. La programmazione difensiva, a prova di guasto, semplifica la manutenzione, dove di solito viene impiegata gran parte del tempo.

Aggiornare

Ho scritto la tua elaborazione. Di solito è utile fornire all'utente finale un'applicazione che funzioni bene con i bug, ovvero che mostri il più possibile. La robustezza è buona, perché il paradiso sa solo cosa si romperà in un momento critico.

Tuttavia, gli sviluppatori e i tester devono essere consapevoli dei bug al più presto, quindi probabilmente si desidera un framework di registrazione con un hook che può visualizzare un avviso all'utente che si è verificato un problema. L'avviso dovrebbe probabilmente essere mostrato a tutti gli utenti, ma probabilmente può essere personalizzato in modo diverso a seconda dell'ambiente di runtime.


afferma che non essere presente in versione è un grande aspetto negativo per me, la pubblicazione è l'ora esatta in cui vuoi che le informazioni aggiunte
jk.

1
Bene, sentiti libero di usare una funzione di lancio condizionale ^ h ^ h ^ h che è attiva anche in versione se ne hai bisogno. Mi sono girato abbastanza spesso.
Macke,

7

Fallire presto, fallire spesso. nullè uno dei migliori valori imprevisti che puoi ottenere, perché puoi fallire rapidamente non appena provi ad usarlo. Altri valori imprevisti non sono così facili da rilevare. Dal momento che nullfallisce automaticamente ogni volta che provi a usarlo, direi che non richiede un controllo esplicito.


28
Spero che volessi dire: fallire presto, fallire velocemente, non spesso ...
empi

12
Il problema è che senza controlli espliciti, a volte un valore null può propagarsi abbastanza lontano prima che provochi un'eccezione, e quindi può essere molto difficile scoprire da dove proviene.
Michael Borgwardt,

@MichaelBorgwardt Non è per questo che le tracce dello stack sono?
R0MANARMY

6
@ R0MANARMY: le tracce dello stack non aiutano quando un valore null viene salvato in un campo e NullPointerException viene lanciata molto più tardi.
Michael Borgwardt,

@ R0MANARMY anche se si è riusciti a fidarsi del numero di riga nella traccia dello stack (in molti casi non è possibile) alcune righe contengono più variabili che potrebbero essere nulle.
Ohad Schneider,

6
  • Controllare un null dovrebbe essere la tua seconda natura

  • La tua API verrà utilizzata da altre persone e le loro aspettative saranno probabilmente diverse dalla tua

  • Test di unità approfonditi normalmente evidenziano la necessità di un controllo di riferimento nullo

  • Il controllo di null non implica istruzioni condizionali. Se temi che il controllo null renda il tuo codice meno leggibile, potresti prendere in considerazione i contratti con codice .NET.

  • Prendi in considerazione l'installazione di ReSharper (o simile) per cercare nel tuo codice i controlli di riferimento null mancanti


L'uso dei contratti di codice per le precondizioni è semplicemente una dichiarazione condizionale glorificata.
un CVn

Sì, sono d'accordo, ma penso anche che il codice appaia più pulito quando si usano i contratti di codice, cioè la sua leggibilità è migliorata.
CodeART

4

Considererei la domanda dal punto di vista della semantica, cioè mi chiedo cosa rappresenta un puntatore NULL o un argomento di riferimento null.

Se una funzione o un metodo foo () ha un argomento x di tipo T, x può essere obbligatorio o facoltativo .

In C ++ puoi passare un argomento obbligatorio come

  • Un valore, ad esempio void foo (T x)
  • Un riferimento, ad esempio void foo (T & x).

In entrambi i casi non si ha il problema di controllare un puntatore NULL. Così, in molti casi, non è necessario un controllo puntatore nullo a tutti per gli argomenti obbligatori.

Se si passa un argomento facoltativo , è possibile utilizzare un puntatore e utilizzare il valore NULL per indicare che non è stato fornito alcun valore:

void foo(T* x)

Un'alternativa è usare un puntatore intelligente come

void foo(shared_ptr<T> x)

In questo caso, probabilmente vorrai controllare il puntatore nel codice, perché fa differenza per il metodo foo () se x contiene un valore o nessun valore.

Il terzo e ultimo caso è che usi un puntatore per un argomento obbligatorio . In questo caso, chiamare foo (x) con x == NULL è un errore e devi decidere come gestirlo (lo stesso che con un indice fuori limite o qualsiasi input non valido):

  1. Nessun controllo: se c'è un bug, lascialo andare in crash e spero che questo errore appaia abbastanza presto (durante il test). In caso contrario (se non si riesce a fallire abbastanza presto), sperare che non venga visualizzato in un sistema di produzione perché l'esperienza dell'utente sarà che vedono l'arresto anomalo dell'applicazione.
  2. Controlla tutti gli argomenti all'inizio del metodo e segnala argomenti NULL non validi, ad esempio lancia un'eccezione da foo () e lascia che qualche altro metodo lo gestisca. Probabilmente la cosa migliore da fare è mostrare all'utente una finestra di dialogo che dice che l'applicazione ha riscontrato un errore interno e sta per essere chiusa.

A parte un modo migliore di fallire (dare all'utente maggiori informazioni), un vantaggio del secondo approccio è che è più probabile che il bug venga trovato: il programma fallirà ogni volta che il metodo viene chiamato con argomenti sbagliati mentre con approccio 1 il bug apparirà solo se viene eseguita un'istruzione che dereferenzia il puntatore (ad es. se viene immesso il ramo destro di un'istruzione if). Quindi l'approccio 2 offre una forma più forte di "fallimento precoce".

In Java hai meno scelte perché tutti gli oggetti vengono passati usando riferimenti che possono sempre essere nulli. Ancora una volta, se il valore null rappresenta un valore NONE di un argomento facoltativo, il controllo del valore null (probabilmente) farà parte della logica di implementazione.

Se il valore null è un input non valido, allora hai un errore e applicherei considerazioni simili come nel caso dei puntatori C ++. Poiché in Java è possibile rilevare eccezioni di puntatore null, l'approccio 1 sopra è equivalente all'approccio 2 se il metodo foo () dereferenzia tutti gli argomenti di input in tutti i percorsi di esecuzione (che probabilmente si verifica molto spesso per metodi di piccole dimensioni).

riassumendo

  • Sia in C ++ che in Java, verificare se gli argomenti opzionali sono null fa parte della logica del programma.
  • In C ++, verificherei sempre gli argomenti obbligatori per assicurarmi che venga generata un'eccezione adeguata in modo che il programma possa gestirla in modo affidabile.
  • In Java (o C #), verificherei gli argomenti obbligatori se ci sono percorsi di esecuzione che non verranno lanciati al fine di avere una forma più forte di "fail early".

MODIFICARE

Grazie a Stilgar per maggiori dettagli.

Nel tuo caso sembra che tu abbia null come risultato di un metodo. Ancora una volta, penso che dovresti prima chiarire (e correggere) la semantica dei tuoi metodi prima di poter prendere una decisione.

Quindi, hai il metodo m1 () che chiama il metodo m2 () e m2 () restituisce un riferimento (in Java) o un puntatore (in C ++) a qualche oggetto.

Qual è la semantica di m2 ()? M2 () dovrebbe sempre restituire un risultato non nullo? In questo caso, un risultato nullo è un errore interno (in m2 ()). Se si controlla il valore restituito in m1 () è possibile avere una gestione degli errori più affidabile. In caso contrario, probabilmente avrai un'eccezione, prima o poi. Forse no. Spero che l'eccezione venga generata durante il test e non dopo la distribuzione. Domanda : se m2 () non deve mai restituire null, perché restituisce null? Forse dovrebbe invece generare un'eccezione? m2 () è probabilmente difettoso.

L'alternativa è che restituire null fa parte della semantica del metodo m2 (), ovvero ha un risultato facoltativo, che dovrebbe essere documentato nella documentazione Javadoc del metodo. In questo caso è OK avere un valore nullo. Il metodo m1 () dovrebbe verificarlo come qualsiasi altro valore: qui non ci sono errori ma probabilmente verificare se il risultato è null fa solo parte della logica del programma.


In ogni caso non era l'argomento che era nullo. Abbiamo fatto una richiesta al database per qualcosa che ci si aspettava fosse presente ma in pratica non era presente. La domanda è se dovremmo verificare che ogni oggetto esista o dovremmo controllare solo se ci aspettiamo che manchi.
Stilgar,

Quindi il risultato restituito da un metodo era un riferimento a un oggetto e null era il valore usato per rappresentare il risultato: NON TROVATO. Ho capito bene?
Giorgio,

Ho aggiornato la domanda con ulteriori dettagli.
Stilgar

3

Il mio approccio generale è non testare mai una condizione di errore che non si sa come gestire . Quindi la domanda a cui è necessario rispondere in ogni istanza specifica diventa: puoi fare qualcosa di ragionevole in presenza di un nullvalore inatteso ?

Se puoi continuare in sicurezza sostituendo un altro valore (una raccolta vuota, diciamo, o un valore o un'istanza predefiniti), allora fallo in ogni caso. L'esempio di @ Shahbaz di World of Warcraft di sostituire un'immagine con un'altra rientra in quella categoria; l'immagine stessa è probabilmente solo decorazione e non ha alcun impatto funzionale . (In una build di debug, userei un colore urlante per attirare l'attenzione sul fatto che si è verificata la sostituzione, ma ciò dipende molto dalla natura dell'applicazione.) Tuttavia, registra i dettagli sull'errore, quindi sai che si sta verificando; altrimenti nasconderai un errore che probabilmente vorrai conoscere. Nasconderlo all'utente è una cosa (di nuovo, supponendo che l'elaborazione possa continuare in modo sicuro); nasconderlo dallo sviluppatore è piuttosto un altro.

Se non puoi continuare in sicurezza in presenza di un valore nullo, fallisci con un errore sensibile; in generale, preferisco fallire al più presto quando è ovvio che l'operazione non può avere successo, il che implica il controllo dei presupposti. Ci sono momenti in cui non vuoi fallire immediatamente, ma rimandare il fallimento il più a lungo possibile; questa considerazione emerge ad esempio nelle applicazioni relative alla crittografia, in cui gli attacchi del canale laterale potrebbero altrimenti divulgare informazioni che non si desidera conoscere. Alla fine, lascia che l'errore si riempia di un gestore di errori generale, che a sua volta registra i dettagli rilevanti e visualizza un messaggio di errore piacevole (per quanto bello possa essere) per l'utente.

Vale anche la pena notare che la risposta corretta può benissimo differire tra build di testing / QA / debug e build di produzione / rilascio. Nelle build di debug, andrei per il fallimento precoce e difficile con messaggi di errore molto dettagliati, per rendere completamente ovvio che c'è un problema e consentire a un post mortem di determinare cosa deve essere fatto per risolverlo. I build di produzione, se confrontati con errori recuperabili in modo sicuro , dovrebbero probabilmente favorire il recupero.


Naturalmente c'è un gestore di errori generale nella catena. Il problema con i registri è che potrebbe non esserci nessuno che li controlli per molto tempo.
Stilgar

@Stilgar, se non c'è nessuno che controlla i log, questo non è un problema con l'esistenza o l'assenza di controlli null.
un CVn

2
C'è un problema. Gli utenti finali potrebbero non notare che il sistema non funziona correttamente per lungo tempo se sono presenti controlli null che nascondono semplicemente l'errore e lo scrivono nei registri.
Stilgar

@Stilgar, ciò dipende interamente dalla natura dell'errore. Come ho detto nel post, solo se puoi continuare in modo sicuro lo null imprevisto dovrebbe essere sostituito / ignorato. L'uso di un'immagine anziché di un'altra in una build di rilascio (nelle build di debug, fallire il prima possibile e il più difficile possibile, quindi il problema diventa ovvio) è una bestia molto diversa rispetto alla restituzione di risultati non validi per un calcolo, ad esempio. (Risposta modificata per includerlo.)
un CVn

1
Vorrei fallire velocemente e presto anche in produzione a meno che tu non riesca in qualche modo a registrare e segnalare l'errore (senza fare troppo affidamento su utenti esperti). Nascondere i bug distribuiti dagli utenti è una cosa: nasconderli da te è pericoloso. Inoltre, consentire all'utente di vivere con bug impercettibili può compromettere la fiducia nell'applicazione. A volte è meglio mordere il proiettile e far loro sapere che c'era un bug e che lo hai risolto rapidamente.
Merlyn Morgan-Graham,

2

Certo NULLnon è previsto , ma ci sono sempre dei bug!

Credo che dovresti verificare NULLse non te lo aspetti. Lascia che ti dia degli esempi (anche se non sono in Java).

  • In World of Warcraft, a causa di un bug, l'immagine di uno degli sviluppatori è apparsa su un cubo per molti oggetti. Immagina se il motore grafico non si aspettasse dei NULL puntatori, ci sarebbe stato un arresto anomalo, mentre si sarebbe trasformato in un'esperienza non così fatale (persino divertente) per l'utente. Il minimo è che non avresti inaspettatamente perso la connessione o uno dei tuoi punti di salvataggio.

    Questo è un esempio in cui il controllo di NULL ha impedito un arresto anomalo e il caso è stato gestito in silenzio.

  • Immagina un programma di cassiere bancario. L'interfaccia esegue alcuni controlli e invia i dati di input a una parte sottostante per eseguire i trasferimenti di denaro. Se la parte principale si aspetta di non ricevere NULL, un bug nell'interfaccia può causare un problema nella transazione, probabilmente una perdita di denaro nel mezzo (o peggio, duplicata).

    Per "un problema" intendo un crash (come in crash crash (diciamo che il programma è scritto in C ++), non un'eccezione puntatore null). In questo caso, la transazione deve essere ripristinata se viene rilevato NULL (il che ovviamente non sarebbe possibile se non si verificassero le variabili rispetto a NULL!)

Sono sicuro che hai avuto l'idea.

Il controllo della validità degli argomenti delle funzioni non ingombra il codice, in quanto si tratta di un paio di controlli nella parte superiore della funzione (non distribuiti nel mezzo) e aumenta notevolmente la robustezza.

Se dovessi scegliere tra "il programma dice all'utente che non ha funzionato correttamente e si spegne o torna indietro in modo coerente, creando eventualmente un registro degli errori" e "Violazione di accesso", non sceglieresti il ​​primo? Ciò significa che ogni funzione dovrebbe essere preparata per essere chiamata da un codice errato piuttosto che aspettarsi che tutto sia corretto, semplicemente perché i bug esistono sempre.


Il tuo secondo esempio confuta il tuo punto. In una transazione bancaria se qualcosa va storto la transazione dovrebbe interrompersi. È proprio quando si copre l'errore che il denaro può essere perso o duplicato. Controllare null sarebbe come "Il cliente invia denaro null ... beh, invierò solo $ 10 (l'equivalente dell'immagine dello sviluppatore)"
Stilgar

@Stilgar, Al contrario! Il controllo di NULL verrebbe interrotto con un messaggio. Non verificare NULL sarebbe un arresto anomalo . Ora l'arresto anomalo all'inizio della transazione potrebbe non essere negativo, ma al centro può essere catastrofico. (Non ho detto che il bonifico bancario dovrebbe gestire l'errore come il gioco! Solo il fatto che dovrebbe gestirlo)
Shahbaz,

2
Il punto centrale di una "transazione" è che non può essere completato a metà :) Se si verifica un errore, viene eseguito il rollback.
Stilgar

Questo è un consiglio terribile per qualsiasi cosa in cui sia richiesta la semantica transazionale. Dovresti fare in modo che tutte le tue transazioni siano atomiche e idempotenti, altrimenti qualsiasi errore che hai sarà di garanzia di risorse perse o duplicate (denaro). Se devi affrontare operazioni non atomiche o non idempotenti, dovresti avvolgerle con molta attenzione in strati che garantiscano un comportamento atomico e idempotente (anche se devi scrivere quei livelli da solo). Pensa sempre: cosa succede se una meteora colpisce il web server nel momento in cui questa transazione è in corso?
Merlyn Morgan-Graham,

1
@ MerlynMorgan-Graham, l'ho fatto. Spero che chiarisca la confusione.
Shahbaz,

2

Come hanno indicato le altre risposte Se non ti aspetti NULL, chiariscilo lanciando ArgumentNullException. A mio avviso, quando esegui il debug del progetto ti aiuta a scoprire prima i difetti nella logica del tuo programma.

Quindi rilascerai il tuo software, se ripristini quei NullRefrencescontrolli, non ti perdi nulla sulla logica del tuo software, ti assicurerai solo che non verrà visualizzato un bug grave.

Un altro punto è che se il NULLcontrollo è sui parametri di una funzione, allora puoi decidere se la funzione è il posto giusto per farlo o no; in caso contrario, trovare tutti i riferimenti alla funzione e quindi prima di passare un parametro alla funzione rivedere gli scenari probabili che possono portare a un riferimento null.


1

Ogni cosa nullnel tuo sistema è una bomba a orologeria in attesa di essere scoperta. Controlla nullai limiti in cui il tuo codice interagisce con il codice che non controlli e proibisci nullall'interno del tuo codice. Questo ti libera dal problema senza ingenuamente confidare nel codice straniero. Utilizzare invece le eccezioni o una sorta di Maybe/ Nothingtipo.


0

Mentre alla fine verrà generata un'eccezione del puntatore null, essa verrà spesso lanciata lontano dal punto in cui è stato ottenuto il puntatore null. Fatevi un favore e abbreviate il tempo necessario per correggere l'errore, registrando almeno il fatto che si ottiene un puntatore null il più presto possibile.

Anche se non sai cosa fare ora, la registrazione dell'evento ti farà risparmiare tempo in seguito. E forse il ragazzo che ottiene il biglietto sarà in grado di utilizzare il tempo risparmiato per capire la risposta corretta.


-1

Dal momento che, Fail early, fail fastcome affermato da @DeadMG (@empi) non è possibile perché l'applicazione Web deve funzionare bene sotto bug, è necessario applicare la regola

nessuna eccezione per la cattura senza registrazione

così come sviluppatori siete a conoscenza di potenziali problemi controllando i registri.

se si utilizza un framework di registrazione come log4net o log4j, è possibile impostare un output di registrazione speciale aggiuntivo (noto anche come appender) che invii errori e errori irreversibili. quindi rimani informato .

[aggiornare]

se l'utente finale della pagina non dovesse essere a conoscenza dell'errore, puoi impostare un appender che ti invii errori e errori irreversibili.

se è ok che l'utente finale della pagina vede errori criptici, puoi impostare un appender che inserisca il log degli errori per l'elaborazione della pagina alla fine della pagina.

Indipendentemente da come si utilizza la registrazione se si ingoia l'eccezione, il programma continua con i dati che hanno senso e la deglutizione non è più silenziosa.


1
La domanda è: cosa succede alla pagina?
Stilgar

Come facciamo a sapere che i dati hanno un senso e che il sistema è in uno stato coerente. Dopo tutto l'errore è stato inaspettato, quindi non sappiamo quale sia stato l'impatto.
Stilgar

"Dato che Fail precoce, fail fast come affermato da @DeadMG (@empi) non è possibile perché l'applicazione web deve funzionare bene sotto bug dovresti applicare la regola": si potrebbero sempre catturare tutte le eccezioni (catch (Throwable t)) e reindirizzare a qualche pagina di errore. Non sarebbe possibile?
Giorgio,

-1

Ci sono già buone risposte a questa domanda, potrebbe esserci un'aggiunta: preferisco affermazioni nel mio codice. Se il tuo codice può funzionare solo con oggetti validi, ma nut con null, devi affermare sul valore null.

Affermazioni in questo tipo di situazione permetterebbero a qualsiasi unità e / o test di integrazione di fallire con il messaggio esatto, quindi è possibile risolvere il problema abbastanza velocemente prima della produzione. Se un tale errore scivola attraverso i test e va alla produzione (dove le asserzioni dovrebbero essere disattivate) riceverai NpEx e un bug grave, ma è meglio, di qualche bug minore che è molto più sfocato e spuntando da qualche altra parte nel codice e sarebbe più costoso da correggere.

Inoltre, se qualcuno deve lavorare con le tue asserzioni sul codice, gli informa delle tue assunzioni fatte durante la progettazione / scrittura del codice (in questo caso: Ehi amico, questo oggetto deve essere presentato!), Che facilita la manutenzione.


Potresti per favore scrivere qualcosa da sottovalutare? Gradirei una discussione. Grazie
GeT,

Non ho espresso il mio voto negativo e sono d'accordo con la tua idea generale. La lingua ha bisogno di un po 'di lavoro però. Le tue asserzioni definiscono il contratto della tua API e fallisci presto / fallisci velocemente se tali contratti vengono violati. Facendo questo sulla skin della tua applicazione, gli utenti del tuo codice sapranno che hanno fatto qualcosa di sbagliato nel loro codice. Se vedono tracciare uno stack nei recessi del tuo codice, probabilmente penseranno che il tuo codice sia difettoso - e potresti pensarlo anche fino a quando non ottieni una solida riprogrammazione ed esegui il debug.
Merlyn Morgan-Graham,

-1

Normalmente cerco null ma "gestisco" null imprevisti lanciando un'eccezione che fornisce un po 'più di dettaglio su dove esattamente si è verificato il null.

Modifica: se ricevi un valore nullo quando non ti aspetti che qualcosa non vada, il programma dovrebbe morire.


-1

Dipende dall'implementazione del linguaggio delle eccezioni. Le eccezioni consentono di intraprendere alcune azioni per prevenire danni ai dati o rilasciare in modo pulito le risorse nel caso in cui il sistema entri in uno stato o una condizione imprevisti. Questo è diverso dalla gestione degli errori che sono condizioni che puoi prevedere. La mia filosofia è che dovresti avere il minor numero di eccezioni possibile. È rumore. Il tuo metodo può fare qualcosa di ragionevole gestendo l'eccezione? Può recuperare? Può riparare o prevenire ulteriori danni? Se hai intenzione di catturare l'eccezione e poi tornare dal metodo o fallire in qualche altro modo innocuo, allora non ha davvero senso catturare l'eccezione. Avresti aggiunto solo rumore e complessità al tuo metodo e potresti nascondere la fonte di un errore nel tuo progetto. In questo caso, lascerei che l'errore si riempia di bolle e venga catturato da un anello esterno. Se puoi fare qualcosa come riparare un oggetto o contrassegnarlo come corrotto o rilasciare alcune risorse critiche come un file di blocco o chiudendo in modo pulito un socket, questo è un buon uso delle eccezioni. Se effettivamente ti aspetti che il NULL venga visualizzato frequentemente come un caso limite valido, devi gestirlo nel normale flusso logico con istruzioni if-the-else o switch-case o altro, non con una gestione delle eccezioni. Ad esempio, un campo disabilitato in un modulo può essere impostato su NULL, che è diverso da una stringa vuota che rappresenta un campo lasciato vuoto. Questa è un'area in cui devi usare il buon senso e il buon senso. Non ho mai sentito parlare di buone regole empiriche su come affrontare questo problema in ogni situazione. Se puoi fare qualcosa come riparare un oggetto o contrassegnarlo come corrotto o rilasciare alcune risorse critiche come un file di blocco o chiudendo in modo pulito un socket, questo è un buon uso delle eccezioni. Se effettivamente ti aspetti che il NULL venga visualizzato frequentemente come un caso limite valido, devi gestirlo nel normale flusso logico con istruzioni if-the-else o switch-case o altro, non con una gestione delle eccezioni. Ad esempio, un campo disabilitato in un modulo può essere impostato su NULL, che è diverso da una stringa vuota che rappresenta un campo lasciato vuoto. Questa è un'area in cui devi usare il buon senso e il buon senso. Non ho mai sentito parlare di buone regole empiriche su come affrontare questo problema in ogni situazione. Se puoi fare qualcosa come riparare un oggetto o contrassegnarlo come corrotto o rilasciare alcune risorse critiche come un file di blocco o chiudendo in modo pulito un socket, questo è un buon uso delle eccezioni. Se effettivamente ti aspetti che il NULL venga visualizzato frequentemente come un caso limite valido, devi gestirlo nel normale flusso logico con istruzioni if-the-else o switch-case o altro, non con una gestione delle eccezioni. Ad esempio, un campo disabilitato in un modulo può essere impostato su NULL, che è diverso da una stringa vuota che rappresenta un campo lasciato vuoto. Questa è un'area in cui devi usare il buon senso e il buon senso. Non ho mai sentito parlare di buone regole empiriche su come affrontare questo problema in ogni situazione.

Java non riesce nella sua eccezione a gestire l'implementazione con "eccezioni controllate" in cui ogni possibile eccezione che potrebbe essere sollevata da oggetti utilizzati in un metodo deve essere rilevata o dichiarata nella clausola "genera" del metodo. È l'opposto di C ++ e Python dove puoi scegliere di gestire le eccezioni come ritieni opportuno. In Java se modifichi il corpo di un metodo per includere una chiamata che potrebbe sollevare un'eccezione, devi affrontare la scelta di aggiungere una gestione esplicita per un'eccezione di cui potresti non preoccuparti, aggiungendo così rumore e complessità al tuo codice, oppure devi aggiungi quell'eccezione alla clausola "genera" del metodo che stai modificando, che non solo aggiunge rumore e disordine, ma modifica la firma del tuo metodo. È quindi necessario inserire un codice di modifica ovunque venga utilizzato il metodo per gestire la nuova eccezione che il metodo ora può sollevare o aggiungere l'eccezione alla clausola "genera" di tali metodi innescando così un effetto domino delle modifiche al codice. "Cattura o specifica requisito" deve essere uno degli aspetti più fastidiosi di Java. L'eccezione eccezione per RuntimeExceptions e la razionalizzazione ufficiale Java per questo errore di progettazione è zoppa.

L'ultimo paragrafo ha poco a che fare con la risposta alla tua domanda. Odio Java.

- Noah


questo post è piuttosto difficile da leggere (wall of text). Ti dispiacerebbe modificarlo in una forma migliore?
moscerino del
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.