È meglio restituire valori NULL o vuoti da funzioni / metodi in cui il valore restituito non è presente?

127

Sto cercando una raccomandazione qui. Sto lottando con se sia meglio restituire NULL o un valore vuoto da un metodo quando il valore restituito non è presente o non può essere determinato.

Prendi i seguenti due metodi come esempi:

string ReverseString(string stringToReverse) // takes a string and reverses it.
Person FindPerson(int personID)    // finds a Person with a matching personID.

In ReverseString() , direi che restituisce una stringa vuota perché il tipo restituito è una stringa, quindi il chiamante se lo aspetta. Inoltre, in questo modo, il chiamante non dovrebbe verificare se è stato restituito un valore NULL.

In FindPerson() , restituire NULL sembra una soluzione migliore. Indipendentemente dal fatto che sia restituito NULL o un oggetto Person vuoto ( new Person() ), il chiamante dovrà verificare se l'oggetto Person è NULL o vuoto prima di fare qualcosa (come chiamare UpdateName() ). Quindi, perché non solo restituire NULL qui e quindi il chiamante deve solo verificare NULL.

Qualcun altro ha problemi con questo? Qualsiasi aiuto o intuizione è apprezzata.

    
posta P B 17.11.2011 - 19:34
fonte

17 risposte

70

StackOverflow ha una buona discussione su questo argomento esatto in questo Q & A . Nella domanda con punteggio più alto, note in karaoke:

Returning null is usually the best idea if you intend to indicate that no data is available.

An empty object implies data has been returned, whereas returning null clearly indicates that nothing has been returned.

Additionally, returning a null will result in a null exception if you attempt to access members in the object, which can be useful for highlighting buggy code - attempting to access a member of nothing makes no sense. Accessing members of an empty object will not fail meaning bugs can go undiscovered.

Personalmente, mi piace restituire stringhe vuote per le funzioni che restituiscono stringhe per ridurre al minimo la quantità di gestione degli errori che deve essere implementata. Tuttavia, dovrai assicurarti che il gruppo con cui lavorerai seguirà la stessa convenzione, altrimenti i benefici di questa decisione non saranno raggiunti.

Tuttavia, poiché il poster nella risposta SO è stato notato, i valori nulli dovrebbero probabilmente essere restituiti se un oggetto è previsto in modo che non ci siano dubbi sul fatto che i dati vengano restituiti.

Alla fine, non esiste un unico modo migliore di fare le cose. Costruire un consenso di squadra alla fine guiderà le migliori pratiche della tua squadra.

    
risposta data 17.11.2011 - 19:42
fonte
77

In tutto il codice che scrivo, evito di restituire null da una funzione. L'ho letto in Pulisci codice .

Il problema con l'utilizzo di null è che la persona che usa l'interfaccia non sa se null è un possibile risultato e se deve controllarlo, perché non c'è nessun tipo di riferimento not null .

In F # puoi restituire un tipo option , che può essere some(Person) o none , quindi è ovvio al chiamante che devono controllare.

L'analogo schema C # (anti-) è il metodo Try... :

public bool TryFindPerson(int personId, out Person result);

Ora so che le persone hanno detto che odiano il pattern Try... perché avere un parametro di output rompe le idee di una funzione pura, ma in realtà non è diverso da:

class FindResult<T>
{
   public FindResult(bool found, T result)
   {
       this.Found = found;
       this.Result = result;
   }

   public bool Found { get; private set; }
   // Only valid if Found is true
   public T Result { get; private set;
}

public FindResult<Person> FindPerson(int personId);

... e ad essere sinceri, si può presumere che il programmatore ogni .NET sia a conoscenza del modello Try... perché è utilizzato internamente dal framework .NET. Ciò significa che non devono leggere la documentazione per capire che cosa fa, che è più importante per me che attenersi alla visione di alcune puristi delle funzioni (capendo che result è un parametro out , non un parametro ref ).

Quindi andrei con TryFindPerson perché sembra che sia perfettamente normale non riuscire a trovarlo.

Se, d'altra parte, non c'è alcun motivo logico che il chiamante possa mai fornire un personId che non esisteva, probabilmente lo farei:

public Person GetPerson(int personId);

... e poi lanciare un'eccezione se non era valida. Il prefisso Get... implica che il chiamante sappia che dovrebbe avere successo.

    
risposta data 17.11.2011 - 19:58
fonte
28

Puoi provare il modello di caso speciale di Martin Fowler, da Paterns of Enterprise Application Architecture :

Nulls are awkward things in object-oriented programs because they defeat polymorphism. Usually you can invoke foo freely on a variable reference of a given type without worrying about whether the item is the exact type or a sub-class. With a strongly typed language you can even have the compiler check that the call is correct. However, since a variable can contain null, you may run into a runtime error by invoking a message on null, which will get you a nice, friendly stack trace.

If it's possible for a variable to be null, you have to remember to surround it with null test code so you'll do the right thing if a null is present. Often the right thing is same in many contexts, so you end up writing similar code in lots of places - committing the sin of code duplication.

Nulls are a common example of such problems and others crop up regularly. In number systems you have to deal with infinity, which has special rules for things like addition that break the usual invariants of real numbers. One of my earliest experiences in business software was with a utility customer who wasn't fully known, referred to as "occupant." All of these imply altering the usual behavior of the type.

Instead of returning null, or some odd value, return a Special Case that has the same interface as what the caller expects.

    
risposta data 17.11.2011 - 19:48
fonte
14

Penso che ReverseString() restituirebbe la stringa invertita e genererebbe un IllegalArgumentException se passati in Null .

Penso che FindPerson() dovrebbe seguire il NullObject Pattern o sollevare un'eccezione non controllata sul fatto di non trovare qualcosa se dovessi sempre essere in grado di trovare qualcosa

Avere a che fare con Null è qualcosa che dovrebbe essere evitato. Avere Null nelle lingue è stato chiamato a Errore di miliardi di dollari dal suo inventore!

I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W).

My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement.

This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.

In recent years, a number of program analysers like PREfix and PREfast in Microsoft have been used to check references, and give warnings if there is a risk they may be non-null. More recent programming languages like Spec# have introduced declarations for non-null references. This is the solution, which I rejected in 1965.

Tony Hoare

    
risposta data 17.11.2011 - 21:24
fonte
11

Restituisce una opzione . Tutti i vantaggi di restituire un valore non valido diverso (come la possibilità di avere valori vuoti nelle raccolte) senza alcun rischio di NullPointerException.

    
risposta data 18.11.2011 - 05:10
fonte
7

Vedo entrambi i lati di questo argomento e mi rendo conto che alcune voci piuttosto influenti (ad esempio, Fowler) sostengono che non restituiscono valori nulli per mantenere il codice pulito, evitare blocchi di gestione degli errori aggiuntivi, ecc.

Tuttavia, tendo a schierarmi con i fautori del rinvio di null. Trovo che ci sia una distinzione importante nel richiamare un metodo e che risponde con Non ho dati e risponde con Ho questa stringa vuota .

Poiché ho visto alcune delle discussioni che fanno riferimento a una classe Person, prendi in considerazione lo scenario in cui cerchi di cercare un'istanza della classe. Se si passa in qualche attributo finder (ad es. Un ID), un client può immediatamente verificare null per vedere se non è stato trovato alcun valore. Questo non è necessariamente eccezionale (quindi non richiede eccezioni), ma dovrebbe anche essere documentato chiaramente. Sì, questo richiede un certo rigore da parte del cliente, e no, non penso che sia una brutta cosa.

Ora considera l'alternativa in cui restituisci un oggetto Persona valido ... che non contiene nulla. Metti i null in tutti i suoi valori (nome, indirizzo, preferitoDrink), oppure vai a popolare quelli con oggetti validi ma vuoti? In che modo il tuo cliente ora stabilisce che non è stata trovata una persona reale? Hanno bisogno di controllare se il nome è una stringa vuota anziché null? Non è questo tipo di cose che in realtà porteranno a un numero maggiore o maggiore di confusione di codice e dichiarazioni condizionali che se avessimo appena controllato il null e avessimo spostato?

Ancora una volta, ci sono punti su entrambi i lati di questa argomentazione su cui potrei essere d'accordo, ma trovo che ciò abbia più senso per la maggior parte delle persone (rendendo il codice più mantenibile).

    
risposta data 17.11.2011 - 22:12
fonte
6

Restituisce null se è necessario sapere se l'elemento esiste o meno. Altrimenti, restituire il tipo di dati previsto. Questo è particolarmente vero se stai restituendo un elenco di elementi. Di solito è sicuro assumere che se il chiamante vuole una lista, vorranno scorrere l'elenco. Molte (più? Tutte?) Lingue falliscono se si tenta di eseguire iterazioni su null ma non quando si itera su un elenco vuoto.

Trovo frustrante provare a usare un codice come questo, solo per fallire:

for thing in get_list_of_things() {
    do_something_clever(thing)
}

Non dovrei aggiungere un caso speciale per il caso quando non ci sono cose .

    
risposta data 17.11.2011 - 19:43
fonte
5

Dipende dalla semantica del metodo. Potresti eventualmente specificare che il tuo metodo accetta solo "Not null". In Java puoi dichiararlo usando alcune annotazioni sui metadati:

string ReverseString(@NotNull String stringToReverse)

Dovresti in qualche modo specificare il valore di ritorno. Se dichiari, accetti solo i valori NotNull, sei obbligato a restituire una stringa vuota (se l'input era anche una stringa vuota).

Il secondo caso è un po 'complicato nella sua semantica. Se questo metodo è quello di restituire qualcuno con la sua chiave primaria (e ci si aspetta che la persona esista), il modo migliore è gettare un'eccezione.

Person FindPerson(int personID)

Se cerchi una persona con un id indovinato (che a me sembra strano), dovresti dichiarare tale metodo come @Nullable.

    
risposta data 17.11.2011 - 19:44
fonte
3

Suggerirei di utilizzare il pattern Null Object ogni volta che è possibile. Semplifica il codice quando si chiamano i metodi e non si legge codice brutto come

if (someObject != null && someObject.someMethod () != whateverValue)

Ad esempio, se un metodo restituisce una raccolta di oggetti che si adattano ad alcuni pattern, restituire una raccolta vuota ha più senso che restituire null e iterare su questa raccolta vuota non avrà praticamente alcuna penalizzazione delle prestazioni. Un altro caso sarebbe un metodo che restituisce l'istanza di classe utilizzata per registrare i dati, a mio parere è preferibile restituire un oggetto Null anziché null , in quanto non obbliga gli utenti a controllare sempre se il riferimento restituito è null .

Nei casi in cui restituire null ha senso (ad esempio chiamando il metodo findPerson () ), proverei almeno a fornire un metodo che restituisce se l'oggetto è presente ( personExists (int personId) per esempio), un altro esempio sarebbe il metodo containsKey () su Map in Java). Rende il codice dei chiamanti più pulito, in quanto si può facilmente vedere che esiste la possibilità che l'oggetto desiderato non sia disponibile (la persona non esiste, la chiave non è presente nella mappa). Controllando costantemente se un riferimento è null offusca il codice secondo me.

    
risposta data 17.11.2011 - 23:49
fonte
3

null è la cosa migliore da restituire se e solo se si applicano le seguenti condizioni:

  • il risultato nullo è previsto durante il normale funzionamento . Ci si potrebbe aspettare che potresti non essere in grado di trovare una persona in alcune circostanze ragionevoli, quindi findPerson () restituire null va bene. Tuttavia, se si tratta di un errore veramente inaspettato (ad esempio in una funzione chiamata saveMyImportantData ()), è necessario generare un'eccezione. C'è un suggerimento nel nome - Le eccezioni sono per circostanze eccezionali!
  • null è usato per significare "non trovato / nessun valore" . Se intendi qualcos'altro, allora restituisci qualcos'altro! Buoni esempi potrebbero essere operazioni in virgola mobile che restituiscono Infinity o NaN - questi sono valori con significati specifici, quindi sarebbe molto confuso se restituissi null qui.
  • La funzione è pensata per restituire un singolo valore , come findPerson (). Se è stato progettato per restituire una raccolta, ad es. findAllOldPeople () quindi una raccolta vuota è la migliore. Un corollario di ciò è che una funzione che restituisce una raccolta non dovrebbe mai restituire null .

Inoltre, assicurati di documentare il fatto che la funzione può restituire null.

Se segui queste regole, i null sono per lo più innocui. Si noti che se si dimentica di verificare la presenza di null, si otterrà immediatamente una NullPointerException subito dopo, che di solito è un bug piuttosto facile da risolvere. Questo approccio veloce è molto meglio di un valore di ritorno falso (ad esempio una stringa vuota) che viene propagato silenziosamente attorno al tuo sistema, probabilmente corrompendo i dati, senza generare un'eccezione.

Infine, se applicate queste regole alle due funzioni elencate nella domanda:

  • FindPerson - sarebbe opportuno che questa funzione restituisse null se la persona non fosse stata trovata
  • ReverseString - sembra che non riuscirebbe mai a trovare un risultato se ha passato una stringa (poiché tutte le stringhe possono essere invertite, inclusa la stringa vuota). Quindi non dovrebbe mai restituire null. Se qualcosa va storto (memoria insufficiente?) Allora dovrebbe generare un'eccezione.
risposta data 12.01.2012 - 06:59
fonte
1

Secondo me c'è una differenza tra restituire NULL, restituire qualche risultato vuoto (ad es. la stringa vuota o una lista vuota) e lanciare un'eccezione.

Normalmente utilizzo il seguente approccio. Considero una funzione o metodo f (v1, ..., vn) chiamata come l'applicazione di una funzione

f : S x T1 x ... x Tn -> T

dove S è lo "stato del mondo" T1, ..., Tn sono i tipi di parametri di input e T è il tipo di ritorno.

Per prima cosa provo a definire questa funzione. Se la funzione è parziale (cioè ci sono alcuni valori di input per i quali non è definita), restituisco NULL per segnalarlo. Questo perché voglio che il calcolo termini normalmente e dimmi che la funzione che ho richiesto non è definita sugli input dati. Utilizzando, ad es., Una stringa vuota come valore di ritorno è ambigua perché potrebbe essere che la funzione sia definita sugli input e la stringa vuota sia il risultato corretto.

Penso che il controllo extra per un puntatore NULL nel codice chiamante sia necessario perché stai applicando una funzione parziale ed è compito del metodo chiamato dirti se la funzione non è definita per l'input specificato.

Preferisco usare eccezioni per errori che non consentono di eseguire il calcolo (cioè non è stato possibile trovare alcuna risposta).

Ad esempio, supponiamo di avere un cliente di classe e voglio implementare un metodo

Customer findCustomer(String customerCode)

per cercare un cliente nel database dell'applicazione tramite il suo codice. In questo metodo, vorrei

  • Restituisce un oggetto di classe Customer se la query ha esito positivo,
  • Restituisce null se la query non trova alcun cliente.
  • Genera un'eccezione se non è possibile connettersi al database.

I controlli extra per null, ad es.

Customer customer = findCustomer("...");
if (customer != null && customer.getOrders() > 0)
{
    ...
}

fanno parte della semantica di quello che sto facendo e non vorrei semplicemente "saltarli" per rendere il codice più leggibile. Non penso sia una buona pratica semplificare la semantica del problema a portata di mano solo per semplificare il codice.

Naturalmente, dato che il controllo per null si verifica molto spesso, è buono se la lingua supporta una sintassi speciale per questo.

Vorrei anche prendere in considerazione l'utilizzo del pattern Null Object (come suggerito da Laf) finché posso distinguere l'oggetto nullo di una classe da tutti gli altri oggetti.

    
risposta data 18.11.2011 - 13:56
fonte
0

I metodi che restituiscono le collezioni dovrebbero restituire vuoto ma altri potrebbero restituire null, perché per le raccolte potrebbe accadere che tu abbia un oggetto ma non ci siano elementi in esso, quindi il chiamante convaliderà solo per le dimensioni piuttosto entrambe.

    
risposta data 18.11.2011 - 10:50
fonte
0

Per me ci sono due casi qui. Se stai restituendo una sorta di elenco, devi sempre restituire l'elenco, a prescindere da cosa, vuoto se non hai nulla da inserire.

L'unico caso in cui vedo un dibattito è quando si restituisce un singolo oggetto. Mi trovo preferendo restituire null in caso di fallimento in una situazione del genere sulla base di far fallire le cose velocemente.

Se si restituisce un oggetto nullo e il chiamante necessitava di un oggetto reale, poteva andare avanti e provare a utilizzare l'oggetto nullo producendo un comportamento imprevisto. Penso che sia meglio che la routine esploda se dimentica di affrontare la situazione. Se qualcosa non va voglio un'eccezione al più presto. È meno probabile che spedisci un bug in questo modo.

    
risposta data 05.05.2012 - 22:37
fonte
0

Aggiungendo ciò che le persone hanno già detto sul costruttore di tipi Maybe :

Un altro grande vantaggio, a parte il non rischiare di produrre NPE, è quello semantico. Nel mondo funzionale, dove ci occupiamo di pure funzioni, ci piace provare a creare funzioni che quando applicate sono esattamente equivalenti al loro valore di ritorno . Considera il caso di String.reverse() : questa è una funzione che, data una certa stringa, rappresenta la versione invertita di quella stringa. Sappiamo che questa stringa esiste, perché ogni stringa può essere invertita (le stringhe sono in pratica set di caratteri ordinati).

Ora, che dire di findCustomer(int id) ? Questa funzione rappresenta il cliente che ha l'ID specificato. Questo è qualcosa che può o non può esistere. Ma ricorda, le funzioni hanno un unico valore di ritorno, quindi non puoi davvero dire "questa funzione restituisce un cliente con l'ID specificato O restituisce null .

Questo è il mio problema principale sia con il ritorno di null e la restituzione di un oggetto nullo. Sono fuorvianti. null non è un cliente e nemmeno "l'assenza di un cliente". È l'assenza di QUALSIASI COSA. È solo un puntatore senza nome di NOTHING. Molto basso livello e molto brutto e molto incline agli errori. Penso che anche un oggetto nullo sia ingannevole. Un cliente null è un cliente. Non è l'assenza di uno, non è il cliente con l'ID richiesto, quindi restituirlo è semplicemente SBAGLIATO. Questo NON è il cliente che ho richiesto, ma continui a dichiarare di aver restituito un cliente. Ha l'ID sbagliato, chiaramente questo è un bug. Rompe il contratto suggerito dal nome del metodo e dalla firma. Richiede che il codice client assuma le cose sul tuo design o che leggi la documentazione.

Entrambi questi problemi sono risolti da un Maybe Customer . All'improvviso, non stiamo dicendo "questa funzione rappresenta il cliente con l'ID specificato". Stiamo dicendo che "questa funzione rappresenta il cliente con l'ID specificato se esiste . Ora è molto chiaro ed esplicito su ciò che fa. Se chiedi al cliente un ID non esistente, riceverà Nothing . Com'è migliore di null ? Non solo per il rischio NPE. Ma anche perché il tipo di Nothing non è solo Maybe . È Maybe Customer . Ha un significato semantico. in particolare significa "l'assenza di un cliente", indicando che tale cliente non esiste.

Un altro problema con null è, naturalmente, che è ambiguo. È che non hai trovato il cliente, o c'era un errore di connessione db o semplicemente non mi è stato permesso di vedere quel cliente?

Se hai diversi casi di errore come questo da gestire, potresti generare eccezioni per tali versioni o (e preferisco in questo modo) potresti restituire un Either CustomerLoadError Customer , che rappresenta qualcosa che è andato storto o il cliente restituito. Ha tutti i vantaggi di Maybe Customer e ti consente anche di specificare cosa può andare storto.

La cosa principale che cerco è di catturare il contratto della funzione nella sua firma. Non assumere le cose, non fare affidamento su convenzioni fugaci, essere esplicito.

    
risposta data 29.05.2016 - 08:02
fonte
0

1) Se la funzione semantica è che non può restituire nulla, il chiamante DEVE testarlo, altrimenti ad esempio. prendi i tuoi soldi e consegnali a nessuno.

2) È buono per fare funzioni che semanticamente restituiscono sempre qualcosa (o throw).

Con un logger , ha senso restituire il logger che non registra se non è stato definito alcun logger. Quando restituisce la raccolta, non ha quasi mai senso (tranne su "livello sufficientemente basso", in cui la raccolta stessa è I dati, non ciò che contiene) per non restituire nulla, perché un set vuoto è un set, non un nulla.

Con un esempio persona , andrei in "ibernazione" (get vs load, IIRC, ma lì è complicato da oggetti proxy per il caricamento lazy) di avere due funzioni, una che restituisce null e il secondo che lancia.

    
risposta data 18.11.2011 - 10:30
fonte
-1
  • NULL deve essere restituito se l'applicazione prevede che i dati siano disponibili ma i dati non sono disponibili. Ad esempio, un servizio che restituisce la CITTA basata su zipcode deve restituire null se la città non viene trovata. Il chiamante può quindi decidere di gestire il null o esplodere.

  • La lista vuota dovrebbe essere restituita se ci sono due possibilità. Dati disponibile o NO dati disponibili. Ad esempio un servizio che ritorna la (e) città (e) se la popolazione è maggiore del numero certo. Può restituisce una lista vuota se non ci sono dati che soddisfano i criteri indicati.

risposta data 04.05.2012 - 17:40
fonte
-2

Il ritorno di NULL è un design terribile , nel mondo orientato agli oggetti. In breve, l'utilizzo di NULL porta a:

  • gestione degli errori ad hoc (anziché eccezioni)
  • semantico ambiguo
  • lento anziché veloce fallito
  • computer thinking vs. object thinking
  • oggetti mutevoli e incompleti

Controlla questo post del blog per una spiegazione dettagliata: link

    
risposta data 13.05.2014 - 09:57
fonte