Domanda

[Questo è il risultato di Best Practice: Le funzioni dovrebbero restituire null o un oggetto vuoto? ma sto cercando di essere molto generale. ]

In un sacco di codice C ++ legacy (um ... production) che ho visto, c'è la tendenza a scrivere molto di NULL (o simile ) verifica i puntatori. Molti di questi vengono aggiunti alla fine di un ciclo di rilascio quando si aggiunge un NULL -check fornisce una soluzione rapida a un arresto anomalo causato dalla discrepanza del puntatore - e non c'è molto tempo per indagare .

Per combattere questo, ho iniziato a scrivere un codice che prendesse un parametro di riferimento ( const ) invece della (molto) tecnica più comune per passare un puntatore. Nessun puntatore, nessun desiderio di cercare NULL (ignorando il caso angolare di avere effettivamente un riferimento null).

In C #, lo stesso C ++ "problema" è presente: il desiderio di verificare ogni riferimento sconosciuto rispetto a null ( ArgumentNullException ) e di correggere rapidamente NullReferenceException aggiungendo un null verifica.

Mi sembra che un modo per evitarlo sia quello di evitare oggetti null in primo luogo usando oggetti vuoti ( String.Empty , EventArgs.Empty ) . Un altro sarebbe lanciare un'eccezione piuttosto che restituire null .

Sto appena iniziando a imparare F #, ma sembra che ci siano molti meno oggetti nulli in quell'ambiente. Quindi forse non devi davvero avere molti riferimenti null in giro?

Sto abbaiando qui l'albero sbagliato?

È stato utile?

Soluzione

Tendo a dubitare del codice con molti NULL e cerco di rimodellarli, ove possibile, con eccezioni, raccolte vuote e così via.

L'oggetto " Introduce Null Object " può anche essere utile il modello di Refactoring di Martin Fowler (pagina 260). Un oggetto nullo risponde a tutti i metodi che un oggetto reale farebbe, ma in un modo che "fa la cosa giusta". Quindi, anziché controllare sempre un ordine per vedere se order.getDiscountPolicy () è NULL, assicurati che in questi casi l'ordine abbia un NullDiscountPolicy. Ciò semplifica la logica di controllo.

Altri suggerimenti

Passare non null solo per evitare un NullReferenceException sta scambiando un problema semplice e facile da risolvere (" esplode perché è null ") per un problema molto più sottile, difficile da debug (" qualcosa diverse chiamate nello stack non si comportano come previsto perché molto prima ha ottenuto un oggetto che non ha informazioni significative ma non è nullo ").

NullReferenceException è una cosa meravigliosa ! Fallisce duro, forte, veloce ed è quasi sempre veloce e facile da identificare e correggere. È la mia eccezione preferita, perché so che quando la vedo, il mio compito richiederà solo circa 2 minuti. In contrasto con un QA confuso o un rapporto del cliente che cerca di descrivere comportamenti strani che devono essere riprodotti e ricondotti all'origine. Yuck.

Tutto si riduce a ciò che tu, come metodo o pezzo di codice, puoi ragionevolmente dedurre dal codice che ti ha chiamato. Se ti viene consegnato un riferimento null e puoi ragionevolmente dedurre ciò che il chiamante avrebbe potuto significare null (forse una raccolta vuota, per esempio?), Allora dovresti assolutamente trattare i null. Tuttavia, se non si può ragionevolmente dedurre cosa fare con un null o cosa significhi il null con null (ad esempio, il codice chiamante ti dice di aprire un file e fornisce la posizione come null), dovresti lanciare ArgumentNullException .

Mantenimento di pratiche di codifica adeguate come questa in ogni "gateway" punto - limiti logici di funzionalità nel codice - NullReferenceExceptions dovrebbe essere molto più rara.

Null ottiene il mio voto. Poi di nuovo, sono della mentalità "fail-fast".

String.IsNullOrEmpty (...) è anche molto utile, suppongo che colga entrambe le situazioni: stringhe nulle o vuote. Potresti scrivere una funzione simile per tutte le lezioni che stai passando.

Se stai scrivendo un codice che restituisce null come condizione di errore, allora non farlo: in generale, dovresti invece lanciare un'eccezione, molto più difficile da perdere.

Se stai consumando codice che ritieni possa restituire null, allora principalmente questi sono eccezioni senza testa : forse esegui alcuni controlli Debug.Assert al chiamante per verificare l'output durante lo sviluppo. Non dovresti veramente avere bisogno di vasti numeri di null nella tua produzione, ma se alcune librerie di terze parti restituiscono molti null s imprevedibilmente, quindi sicuro: fai i controlli.

In 4.0, potresti voler esaminare i contratti di codice; questo ti dà un controllo molto migliore per dire "questo argomento non dovrebbe mai essere passato come null", "questa funzione non restituisce mai null", ecc. e fare in modo che il sistema convalidi tali affermazioni durante l'analisi statica (cioè quando si crea).

La cosa su null è che non ha significato. È semplicemente l'assenza di un oggetto.

Quindi, se intendi davvero una stringa / raccolta vuota / qualunque cosa, restituisci sempre l'oggetto rilevante e mai null . Se la lingua in questione ti consente di specificarlo, fallo.

Nel caso in cui si desideri restituire qualcosa che non significa un valore identificabile con il tipo statico, si hanno diverse opzioni. Restituire null è una risposta, ma senza un significato è un po 'pericoloso. Generare un'eccezione potrebbe in realtà essere ciò che intendi. Potresti voler estendere il tipo con casi speciali (probabilmente con polimorfismo, vale a dire lo Special Case Pattern (un caso speciale del quale è il Null Object Pattern)). Potresti voler racchiudere il valore restituito in un tipo con più significato. Oppure potresti voler passare un oggetto callback. Di solito ci sono molte scelte.

Direi che dipende. Per un metodo che restituisce un singolo oggetto, generalmente restituisco null. Per un metodo che restituisce una raccolta, in genere restituisco una raccolta vuota (non nulla). Questi sono più sulla falsariga delle linee guida che delle regole, tuttavia.

Se sei seriamente intenzionato a programmare in un " null meno " ambiente, considera di utilizzare i metodi di estensione più spesso, sono immuni a NullReferenceExceptions e almeno "fingi"; che null non c'è più:

public static GetExtension(this string s)
{
    return (new FileInfo(s ?? "")).Extension;
}

che può essere chiamato come:

// this code will never throw, not even when somePath becomes null
string somePath = GetDataFromElseWhereCanBeNull();
textBoxExtension.Text = somePath.GetExtension(); 

Lo so, questa è solo convenienza e molte persone considerano correttamente la violazione dei principi di OO (anche se il "fondatore" di OO, Bertrand Meyer, considera null il male e lo bandì completamente dal suo OO design, che viene applicato al linguaggio Eiffel, ma questa è un'altra storia). EDIT: Dan menziona che Bill Wagner ( C # più efficace ) considera la cattiva pratica e ha ragione. Hai mai considerato il metodo di estensione IsNull ;-)?

Per rendere più leggibile il codice, potrebbe essere presente un altro suggerimento: utilizzare l'operatore a coalescenza nulla più spesso per designare un valore predefinito quando un oggetto è null:

// load settings
WriteSettings(currentUser.Settings ?? new Settings());

// example of some readonly property
public string DisplayName
{
    get 
    {
         return (currentUser ?? User.Guest).DisplayName
    }
}

Nessuno di questi elimina il controllo occasionale per null (e ?? non è altro che un if-branch nascosto). Preferisco il meno null nel mio codice, semplicemente perché credo che renda il codice più leggibile. Quando il mio codice è ingombro di istruzioni if ??per null , so che c'è qualcosa di sbagliato nel design e refactoring. Suggerisco a chiunque di fare lo stesso, ma so che le opinioni variano molto sulla questione.

(Aggiornamento) Confronto con eccezioni

Non menzionato nella discussione finora è la somiglianza con la gestione delle eccezioni. Quando ti ritrovi a ignorare onnipresentemente null ogni volta che lo consideri sulla tua strada, è praticamente lo stesso della scrittura:

try 
{
    //...code here...
}
catch (Exception) {}

che ha l'effetto di rimuovere qualsiasi traccia delle eccezioni solo per scoprire che genera eccezioni non correlate molto più tardi nel codice. Sebbene ritenga opportuno evitare di usare null , come menzionato prima in questa discussione, avere null per casi eccezionali è buono. Non nasconderli in blocchi null-ignore, finirà per avere lo stesso effetto dei blocchi catch-all-exceptions.

Per i protagonisti delle eccezioni di solito derivano dalla programmazione transazionale e da forti garanzie di sicurezza delle eccezioni o linee guida cieche. In qualsiasi discreta complessità, vale a dire. flusso di lavoro asincrono, I / O e soprattutto codice di rete sono semplicemente inappropriati. Il motivo per cui vedi documenti in stile Google sull'argomento in C ++, così come tutto il buon codice asincrono 'non applicarlo' (pensa anche ai tuoi pool gestiti preferiti).

C'è di più e sebbene possa sembrare una semplificazione, è davvero così semplice. Per uno otterrai molte eccezioni in qualcosa che non è stato progettato per un uso eccezionale delle eccezioni .. comunque sto divagando, ho letto su questo dai migliori progettisti di biblioteche del mondo, il solito posto è boost (solo non mescolarlo con l'altro campo in crescita che ama le eccezioni, perché hanno dovuto scrivere software musicali :-).

Nel tuo caso, e questa non è la competenza di Fowler, un idioma efficiente di "oggetto vuoto" è possibile solo in C ++ a causa del meccanismo di casting disponibile (forse ma certamente non sempre per mezzo di dominio ). D'altra parte, nel tuo tipo null sei in grado di generare eccezioni e fare quello che vuoi preservando il sito di chiamata pulito e la struttura del codice.

In C # la tua scelta può essere una singola istanza di un tipo che è buona o non valida; come tale è in grado di lanciare accettazioni o semplicemente correre com'è. Quindi potrebbe o meno violare altri contratti (dipende da te ciò che pensi sia meglio a seconda della qualità del codice che stai affrontando).

Alla fine, ripulisce i siti di chiamata, ma non dimenticare che dovrai affrontare uno scontro con molte librerie (e in particolare i ritorni dai contenitori / dizionari, le menti degli iteratori che mi vengono in mente e qualsiasi altro codice di "interfacciamento" per il mondo esterno). Inoltre i controlli null-as-value sono pezzi di codice macchina estremamente ottimizzati, qualcosa da tenere a mente ma concorderò ogni giorno l'uso del puntatore selvaggio senza comprendere costanza, riferimenti e altro porterà a diversi tipi di mutabilità, aliasing e problemi perf .

Per aggiungere, non esiste un proiettile d'argento e lo schianto su riferimento null o l'utilizzo di un riferimento null nello spazio gestito, o il lancio e la non gestione di un'eccezione è un problema identico, nonostante ciò che il mondo gestito ed eccezioni cercherà di venderti. Qualsiasi ambiente decente offre una protezione da questi (diamine puoi installare qualsiasi filtro su qualsiasi sistema operativo che desideri, cos'altro pensi che facciano le macchine virtuali) e ci sono così tanti altri vettori di attacco che questo è stato fatto a pezzi. Inserisci di nuovo la verifica x86 da Google, il loro modo di fare molto più velocemente e meglio "IL", codice "dinamico", ecc.

Sostieni il tuo istinto su questo, pondera i pro e i contro e localizza gli effetti .. in futuro il tuo compilatore ottimizzerà comunque tutto quel controllo, e molto più efficacemente di qualsiasi metodo umano di runtime o compilazione (ma non come facilmente per l'interazione tra i moduli).

Cerco di evitare di restituire null da un metodo ove possibile. Esistono generalmente due tipi di situazioni: quando il risultato nullo sarebbe legale e quando non dovrebbe mai accadere.

Nel primo caso, quando nessun risultato è legale, ci sono diverse soluzioni disponibili per evitare risultati nulli e controlli null ad essi associati: Pattern oggetto nullo e Pattern di casi speciali sono lì per restituire oggetti sostitutivi che non fanno nulla o che fanno qualcosa di specifico in circostanze specifiche.

Se è legale non restituire alcun oggetto, ma non ci sono ancora sostituti adeguati in termini di Null Object o Special Case, allora di solito uso Opzione tipo funzionale - Posso quindi restituire un'opzione vuota quando non c'è risultato legale. Spetta quindi al cliente vedere qual è il modo migliore per gestire l'opzione vuota.

Infine, se non è legale avere alcun oggetto restituito da un metodo, semplicemente perché il metodo non può produrre il suo risultato se manca qualcosa, allora scelgo di lanciare un'eccezione e tagliare ulteriori esecuzioni.

Non puoi sempre restituire un oggetto vuoto, perché 'vuoto' non è sempre definito. Ad esempio, cosa significa che int, float o bool sono vuoti?

Restituire un puntatore NULL non è necessariamente una cattiva pratica, ma penso che sia una pratica migliore restituire un riferimento (const) (dove ha senso farlo ovviamente).

E recentemente ho usato spesso una classe Fallible:

Fallible<std::string> theName = obj.getName();
if (theName)
{
    // ...
}

Esistono varie implementazioni disponibili per tale classe (controlla Google Ricerca Codice), Ho anche creato il mio .

In che modo gli oggetti vuoti sono migliori degli oggetti null? Stai solo rinominando il sintomo. Il problema è che i contratti per le tue funzioni sono troppo vagamente definiti "questa funzione potrebbe restituire qualcosa di utile o potrebbe restituire un valore fittizio" (dove il valore fittizio potrebbe essere nullo, un "oggetto vuoto", o una costante magica come -1 . Ma non importa come esprimi questo valore fittizio, i chiamanti ancora devono verificarlo prima di utilizzare il valore restituito.

Se vuoi ripulire il tuo codice, la soluzione dovrebbe essere quella di restringere la funzione in modo che non restituisca un valore fittizio in primo luogo.

Se si dispone di una funzione che potrebbe restituire un valore o potrebbe non restituire nulla, i puntatori sono un modo comune (e valido) per esprimerlo. Ma spesso, il tuo codice può essere refactored in modo da rimuovere questa incertezza. Se puoi garantire che una funzione restituisca qualcosa di significativo, i chiamanti possono fare affidamento sul fatto che restituisce qualcosa di significativo e quindi non devono controllare il valore restituito.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top