Ho sentito alcune voci affermare che il controllo di un valore null restituito dai metodi non è corretto. Mi piacerebbe sentire alcune ragioni per questo.
pseudocodice:
variable x = object.method()
if (x is null) do something
Ho sentito alcune voci affermare che il controllo di un valore null restituito dai metodi non è corretto. Mi piacerebbe sentire alcune ragioni per questo.
pseudocodice:
variable x = object.method()
if (x is null) do something
Risposte:
La logica alla base della non restituzione di null è che non è necessario verificarlo e quindi il codice non deve seguire un percorso diverso in base al valore restituito. Potresti voler controllare il modello di oggetti nulli che fornisce ulteriori informazioni al riguardo.
Ad esempio, se dovessi definire un metodo in Java che ha restituito una raccolta, in genere preferirei restituire una raccolta vuota (cioè Collections.emptyList()
) piuttosto che nulla in quanto significa che il mio codice client è più pulito; per esempio
Collection<? extends Item> c = getItems(); // Will never return null.
for (Item item : c) { // Will not enter the loop if c is empty.
// Process item.
}
... che è più pulito di:
Collection<? extends Item> c = getItems(); // Could potentially return null.
// Two possible code paths now so harder to test.
if (c != null) {
for (Item item : c) {
// Process item.
}
}
null
è un "contenitore", che contiene zero o un puntatore agli oggetti. (Questo è certamente il modo in cui è esplicitamente modellato da Haskell Maybe
in quei casi, ed è tanto meglio per farlo.)
Ecco il motivo.
In Clean Code di Robert Martin scrive che restituire null è un cattivo design quando si può invece restituire, diciamo, array vuoto. Poiché il risultato atteso è un array, perché no? Ti consentirà di ripetere il risultato senza condizioni aggiuntive. Se è un numero intero, forse 0 sarà sufficiente, se è un hash, hash vuoto. eccetera.
La premessa è di non forzare il codice chiamante per gestire immediatamente i problemi. Il codice chiamante potrebbe non volersi preoccupare di loro. Questo è anche il motivo per cui in molti casi le eccezioni sono migliori di zero.
Buoni usi di restituzione null:
Usi errati: cercare di sostituire o nascondere situazioni eccezionali come:
In quei casi, è più appropriato lanciare un'eccezione poiché:
Tuttavia, le eccezioni non devono essere utilizzate per gestire le normali condizioni operative del programma come:
boolean login(String,String)
sembra che AuthenticationContext createAuthContext(String,String) throws AuthenticationException
Sì, restituire NULL è un design terribile , nel mondo orientato agli oggetti. In breve, l'utilizzo di NULL porta a:
Controlla questo post sul blog per una spiegazione dettagliata: http://www.yegor256.com/2014/05/13/why-null-is-bad.html . Maggiori informazioni nel mio libro Oggetti eleganti , sezione 4.1.
bool TryGetBlah(out blah)
O FirstOrNull()
oppure MatchOrFallback(T fallbackValue)
.
isEmpty()
) e solo se è vero quindi chiamare il metodo. Le persone discutono contro il secondo affermando che sono prestazioni peggiori, ma come dice Unix Philosophy, "valorizzano il tempo umano rispetto al tempo macchina" (ovvero le prestazioni banalmente più lente sprecano meno tempo rispetto agli sviluppatori che eseguono il debug del codice che genera errori spuri).
Chi dice che questo è cattivo design?
Il controllo di null è una pratica comune, anche incoraggiata, altrimenti si corre il rischio di NullReferenceExceptions ovunque. È meglio gestire l'errore con garbo piuttosto che generare eccezioni quando non è necessario.
Sulla base di ciò che hai detto finora, penso che non ci siano abbastanza informazioni.
Restituire null da un metodo CreateWidget () sembra male.
Restituire null da un metodo FindFooInBar () sembra a posto.
Create...
restituisce una nuova istanza o genera ; Get...
restituisce un'istanza esistente prevista o genera ; GetOrCreate...
restituisce un'istanza esistente o una nuova istanza se nessuna esiste o genera ; Find...
restituisce un'istanza esistente, se esiste, onull
. Per le query di raccolta: Get...
restituisce sempre una raccolta, che è vuota se non viene trovato alcun elemento corrispondente.
Il suo inventore dice che è un errore di miliardi di dollari!
Dipende dalla lingua che stai usando. Se sei in una lingua come C # in cui il modo idiomatico di indicare la mancanza di un valore è restituire null, allora restituire null è un buon progetto se non hai un valore. In alternativa, in linguaggi come Haskell che usano idiomaticamente la monade Maybe per questo caso, restituire null sarebbe un cattivo progetto (se fosse possibile).
null
in linguaggi come C # e Java sia spesso sovraccarica e abbia un certo significato nel dominio. Se cerchi la null
parola chiave nelle specifiche della lingua, significa semplicemente "un puntatore non valido". Ciò probabilmente non significa nulla in alcun dominio problematico.
null
o "non inizializzato"null
Se leggi tutte le risposte diventa chiaro che la risposta a questa domanda dipende dal tipo di metodo.
In primo luogo, quando accade qualcosa di eccezionale (IOproblema ecc.), Si generano eccezioni logiche. Quando esattamente qualcosa è eccezionale è probabilmente qualcosa per un argomento diverso ..
Ogni volta che ci si aspetta che un metodo non abbia risultati, ci sono due categorie:
Fino a quando non avremo un modo ufficiale per indicare che una funzione può o non può restituire null, provo ad avere una convenzione di denominazione per indicarlo.
Proprio come hai la convenzione Try Something () per i metodi che dovrebbero fallire, spesso nomina i miei metodi Safe Something () quando il metodo restituisce un risultato neutro anziché null.
Non sono ancora completamente d'accordo con il nome, ma non ho potuto trovare qualcosa di meglio. Quindi sto correndo con quello per ora.
Ho una convention in quest'area che mi ha servito bene
Per query a singolo elemento:
Create...
restituisce una nuova istanza o generaGet...
restituisce un'istanza esistente prevista o generaGetOrCreate...
restituisce un'istanza esistente o una nuova istanza se nessuna esiste o generaFind...
restituisce un'istanza esistente, se esiste, onull
Per le query di raccolta:
Get...
restituisce sempre una raccolta, che è vuota se non viene trovato alcun elemento [1] corrispondente[1] dati alcuni criteri, espliciti o impliciti, indicati nel nome della funzione o come parametri.
Get
quando mi aspetto che sia lì, quindi se non c'è, allora è un errore e lancio - Non ho mai bisogno di controllare il valore di ritorno. Lo uso Find
se davvero non so se è lì o no - quindi devo controllare il valore di ritorno.
Le eccezioni sono per tutte le circostanze eccezionali .
Se la tua funzione è destinata a trovare un attributo associato a un determinato oggetto e tale oggetto non ha tale attributo, potrebbe essere appropriato restituire null. Se l'oggetto non esiste, lanciare un'eccezione può essere più appropriato. Se la funzione ha lo scopo di restituire un elenco di attributi, e non ce ne sono nessuno da restituire, ha senso restituire un elenco vuoto: stai restituendo tutti gli attributi zero.
Va bene restituire null se farlo è significativo in qualche modo:
public String getEmployeeName(int id){ ..}
In un caso come questo è significativo restituire null se l'id non corrisponde a un'entità esistente, in quanto consente di distinguere il caso in cui non è stata trovata alcuna corrispondenza da un errore legittimo.
Le persone potrebbero pensare che ciò sia negativo perché può essere abusato di un valore di ritorno "speciale" che indica una condizione di errore, che non è così buona, un po 'come restituire i codici di errore da una funzione ma confuso perché l'utente deve controllare il ritorno per null, invece di cogliere le opportune eccezioni, ad es
public Integer getId(...){
try{ ... ; return id; }
catch(Exception e){ return null;}
}
Non è necessariamente un cattivo design - come con così tante decisioni di design, dipende.
Se il risultato del metodo è qualcosa che non avrebbe un buon risultato nell'uso normale, la restituzione di null va bene:
object x = GetObjectFromCache(); // return null if it's not in the cache
Se davvero ci dovesse essere sempre un risultato non nullo, potrebbe essere meglio generare un'eccezione:
try {
Controller c = GetController(); // the controller object is central to
// the application. If we don't get one,
// we're fubar
// it's likely that it's OK to not have the try/catch since you won't
// be able to really handle the problem here
}
catch /* ... */ {
}
Optional<>
Per alcuni scenari, si desidera notare un errore non appena si verifica.
Controllare contro NULL e non affermare (per errori del programmatore) o lanciare (per errori dell'utente o del chiamante) nel caso di errore può significare che i crash successivi sono più difficili da rintracciare, perché il caso dispari originale non è stato trovato.
Inoltre, ignorare gli errori può portare a exploit di sicurezza. Forse la nullità deriva dal fatto che un buffer è stato sovrascritto o simili. Ora, si sta senza schiantarsi, il che significa che lo sfruttatore ha la possibilità di eseguire nel codice.
Quali alternative vedi per restituire null?
Vedo due casi:
In questo caso potremmo: Restituire Null o lanciare un'eccezione (selezionata) (o magari creare un oggetto e restituirlo)
In questo caso potremmo restituire Null, restituire un elenco vuoto o generare un'eccezione.
Credo che il ritorno null possa essere meno buono delle alternative in quanto richiede al client di ricordare di verificare la presenza di null, i programmatori dimenticano e codificano
x = find();
x.getField(); // bang null pointer exception
In Java, il lancio di un'eccezione controllata, RecordNotFoundException, consente al compilatore di ricordare al client di gestire il caso.
Trovo che le ricerche che restituiscono elenchi vuoti possano essere abbastanza convenienti: basta popolare il display con tutto il contenuto dell'elenco, oh è vuoto, il codice "funziona".
A volte, restituire NULL è la cosa giusta da fare, ma in particolare quando si ha a che fare con sequenze di diversi tipi (array, elenchi, stringhe, what-have-you) è probabilmente meglio restituire una sequenza di lunghezza zero, in quanto porta a un codice più breve e, si spera, più comprensibile, pur non impiegando molto di più a scrivere dall'implementatore API.
Fagli chiamare un altro metodo dopo il fatto per capire se la chiamata precedente era nulla. ;-) Ehi, è stato abbastanza buono per JDBC
L'idea alla base di questo thread è programmare in modo difensivo. Cioè, codice contro l'imprevisto. Esiste una serie di risposte diverse:
Adamski suggerisce di guardare Null Object Pattern, con quella risposta votata per quel suggerimento.
Michael Valenty suggerisce anche una convenzione di denominazione per dire allo sviluppatore cosa ci si può aspettare. ZeroConcept suggerisce un uso corretto di Exception, se questo è il motivo del NULL. E altri.
Se stabiliamo la "regola" di voler sempre programmare in modo difensivo, allora possiamo vedere che questi suggerimenti sono validi.
Ma abbiamo 2 scenari di sviluppo.
Classi "create" da uno sviluppatore: l'autore
Classi "consumate" da un altro (forse) sviluppatore: lo sviluppatore
Indipendentemente dal fatto che una classe restituisca NULL per i metodi con un valore di ritorno o meno, lo sviluppatore dovrà verificare se l'oggetto è valido.
Se lo sviluppatore non può farlo, allora quel metodo / classe non è deterministico. Cioè, se la "chiamata del metodo" per ottenere l'oggetto non fa ciò che "pubblicizza" (ad esempio getEmployee) ha infranto il contratto.
Come autore di una classe, voglio sempre essere gentile e difensivo (e deterministico) quando creo un metodo.
Quindi, dato che NULL o NULL OBJECT (ad es. If (impiegato come NullEmployee.ISVALID)) devono essere controllati e che potrebbe essere necessario che si verifichi con una raccolta di dipendenti, l'approccio a oggetti nulli è l'approccio migliore.
Ma mi piace anche il suggerimento di Michael Valenty di nominare il metodo che DEVE restituire null, ad esempio getEmployeeOrNull.
Un autore che genera un'eccezione sta rimuovendo la scelta per lo sviluppatore di testare la validità dell'oggetto, il che è molto negativo per una raccolta di oggetti e costringe lo sviluppatore a gestire le eccezioni quando si ramifica il proprio codice di consumo.
Come sviluppatore che consuma la classe, spero che l'autore mi dia la possibilità di evitare o programmare la situazione nulla che la sua classe / i suoi metodi potrebbero dover affrontare.
Quindi come sviluppatore programmerei difensivamente contro NULL da un metodo. Se l'autore mi ha dato un contratto che restituisce sempre un oggetto (NULL OBJECT lo fa sempre) e quell'oggetto ha un metodo / proprietà con cui testare la validità dell'oggetto, allora userei quel metodo / proprietà per continuare a usare l'oggetto , altrimenti l'oggetto non è valido e non posso usarlo.
In conclusione, l'autore della classe / dei metodi deve fornire meccanismi che uno sviluppatore può utilizzare nella sua programmazione difensiva. Cioè, un'intenzione più chiara del metodo.
Lo sviluppatore dovrebbe sempre utilizzare la programmazione difensiva per testare la validità degli oggetti restituiti da un'altra classe / metodo.
Saluti
GregJF
Altre opzioni a questo sono: restituire un valore che indica il successo o meno (o il tipo di un errore), ma se hai solo bisogno di un valore booleano che indichi il successo / fallimento, restituendo null per errore e un oggetto per il successo non lo farebbe essere meno corretto, quindi restituire vero / falso e ottenere l'oggetto tramite il parametro.
Un altro approccio potrebbe usare l'eccezione per indicare i fallimenti, ma qui - ci sono in realtà molte più voci, che dicono che questa è una cattiva pratica (poiché usare le eccezioni può essere conveniente ma ha molti svantaggi).
Quindi personalmente non vedo nulla di male nel restituire null come indicazione che qualcosa è andato storto e controllarlo in seguito (per sapere effettivamente se ci sei riuscito o no). Inoltre, pensare ciecamente che il tuo metodo non restituirà NULL e quindi basare il tuo codice su di esso, può portare ad altri errori, a volte difficili da trovare (anche se nella maggior parte dei casi si bloccherà semplicemente il tuo sistema :), come ti riferirai a 0x00000000 prima o poi).
Durante lo sviluppo di programmi complessi possono insorgere funzioni nulle indesiderate e, come nel caso del codice morto, tali eventi indicano gravi difetti nelle strutture del programma.
Una funzione o un metodo null viene spesso utilizzato come comportamento predefinito di una funzione revectorable o metodo overrideable in un framework di oggetti.
Bene, dipende sicuramente dallo scopo del metodo ... A volte, una scelta migliore sarebbe quella di lanciare un'eccezione. Tutto dipende da caso a caso.
Se il codice è simile a:
command = get_something_to_do()
if command: # if not Null
command.execute()
Se hai un oggetto fittizio il cui metodo execute () non fa nulla e lo restituisci al posto di Null nei casi appropriati, non devi controllare il caso Null e puoi invece semplicemente fare:
get_something_to_do().execute()
Quindi, qui il problema non è tra la ricerca di NULL rispetto a un'eccezione, ma è invece tra il chiamante che deve gestire i non casi speciali in modo diverso (in qualsiasi modo) o meno.
Per il mio caso d'uso avevo bisogno di restituire una mappa dal metodo e quindi cercare una chiave specifica. Ma se restituisco una mappa vuota, allora porterà a NullPointerException e quindi non sarà molto diverso restituendo null invece di una mappa vuota. Ma da Java8 in poi potremmo usare Opzionale . Quanto sopra è la vera ragione per cui è stato introdotto il concetto opzionale.
Buongiorno,
Restituire NULL quando non si è in grado di creare un nuovo oggetto è pratica standard per molte API.
Perché diavolo è un cattivo design non ne ho idea.
Modifica: questo vale per le lingue in cui non si hanno eccezioni come C, dove è stata la convenzione per molti anni.
HTH
'Avahappy,