I riferimenti null sono davvero una brutta cosa?

153

Ho sentito dire che l'inclusione di riferimenti nulli nei linguaggi di programmazione è "l'errore da miliardi di dollari". Ma perché? Certo, possono causare NullReferenceExceptions, ma allora? Qualsiasi elemento della lingua può essere fonte di errori se usato in modo improprio.

E qual è l'alternativa? Suppongo invece di dire questo:

Customer c = Customer.GetByLastName("Goodman"); // returns null if not found
if (c != null)
{
    Console.WriteLine(c.FirstName + " " + c.LastName + " is awesome!");
}
else { Console.WriteLine("There was no customer named Goodman.  How lame!"); }

Potresti dire questo:

if (Customer.ExistsWithLastName("Goodman"))
{
    Customer c = Customer.GetByLastName("Goodman") // throws error if not found
    Console.WriteLine(c.FirstName + " " + c.LastName + " is awesome!"); 
}
else { Console.WriteLine("There was no customer named Goodman.  How lame!"); }

Ma come va meglio? In ogni caso, se dimentichi di verificare che il cliente esista, ottieni un'eccezione.

Suppongo che una CustomerNotFoundException sia un po 'più facile da eseguire il debug rispetto a una NullReferenceException in quanto è più descrittiva. È tutto qui?

    
posta Tim Goodman 23.07.2017 - 15:31
fonte

21 risposta

92

null è il male

C'è una presentazione su InfoQ su questo argomento: Riferimenti Null: The Billion Dollar Mistake di Tony Hoare

Tipo di opzione

L'alternativa alla programmazione funzionale utilizza un tipo di opzione , che può contenere SOME value o NONE .

Un buon articolo Il pattern "Opzione" che discute il tipo di opzione e fornire un'implementazione di esso per Java.

Ho anche trovato un bug report per Java su questo problema: Aggiungi i tipi di opzione Nice a Java per prevenire NullPointerExceptions . La funzione richiesta è stata introdotta in Java 8.

    
risposta data 13.07.2018 - 14:11
fonte
126

Il problema è che, in teoria, qualsiasi oggetto può essere un null e lanciare un'eccezione quando si tenta di usarlo, il codice orientato agli oggetti è fondamentalmente un insieme di bombe inesplose.

Hai ragione che la gestione degli errori aggraziati può essere funzionalmente identica alle dichiarazioni% control if . Ma cosa succede quando qualcosa che ti sei convinto di non poter possibilmente essere un nulla è, di fatto, un nulla? Kerboom. Qualunque cosa accada dopo, sono disposto a scommettere che 1) non sarà aggraziato e 2) non ti piacerà.

E non ignorare il valore di "facile eseguire il debug". Il codice di produzione maturo è una creatura folle e tentacolare; tutto ciò che ti dà più informazioni su cosa è andato storto e dove potrebbe farti risparmiare ore di scavare.

    
risposta data 18.10.2010 - 21:05
fonte
49

Ci sono diversi problemi con l'utilizzo di riferimenti null nel codice.

In primo luogo, viene generalmente utilizzato per indicare uno stato speciale . Anziché definire una nuova classe o costante per ogni stato in quanto le specializzazioni vengono normalmente eseguite, l'utilizzo di un riferimento null utilizza un tipo / valore con perdita generalizzata e generalizzato .

In secondo luogo, il codice di debug diventa più difficile quando appare un riferimento nullo e tu tenti di determinare cosa lo ha generato , quale stato è in vigore e la sua causa anche se puoi tracciare il suo percorso di esecuzione a monte.

In terzo luogo, i riferimenti null introducono percorsi di codice aggiuntivi da testare .

In quarto luogo, una volta che i riferimenti null vengono utilizzati come stati validi per i parametri e i valori restituiti, la programmazione difensiva (per gli stati causati dalla progettazione) richiede più controlli di riferimento null da eseguire in vari luoghi ... solo nel caso.

Quinto, il runtime della lingua sta già eseguendo controlli tipo quando esegue la ricerca di selettori sulla tabella dei metodi di un oggetto. Quindi stai duplicando lo sforzo controllando se il tipo di oggetto è valido / non valido e poi facendo in modo che il runtime verifichi il tipo dell'oggetto valido per richiamare il suo metodo.

Perché non utilizzare il modello NullObject per approfittare del controllo del runtime per averlo invoca i metodi NOP specifici per quello stato (conformi all'interfaccia dello stato regolare) eliminando anche tutti i controlli extra per riferimenti null in tutto la tua base di codice?

È richiede più lavoro creando una classe NullObject per ogni interfaccia con cui desideri rappresentare uno stato speciale. Ma almeno la specializzazione è isolata per ogni stato speciale, piuttosto che il codice in cui lo stato potrebbe essere presente. IOW, il numero di test è ridotto perché hai meno percorsi di esecuzione alternativi nei tuoi metodi.

    
risposta data 18.10.2010 - 23:00
fonte
46

I nulli non sono poi così male, a meno che non li stai aspettando. dovresti dover specificare esplicitamente nel codice che stai aspettando null, ovvero un problema di progettazione della lingua. Considera questo:

Customer? c = Customer.GetByLastName("Goodman");
// note the question mark -- 'c' is either a Customer or null
if (c != null)
{
    // c is not null, therefore its type in this block is
    // now 'Customer' instead of 'Customer?'
    Console.WriteLine(c.FirstName + " " + c.LastName + " is awesome!");
}
else { Console.WriteLine("There was no customer named Goodman.  How lame!"); }

Se provi a invocare metodi su Customer? , dovresti ottenere un errore in fase di compilazione. Il motivo per cui più lingue non lo fanno (IMO) è che non forniscono il tipo di variabile da modificare a seconda dell'ambito in cui si trova. Se la lingua è in grado di gestirlo, allora il problema può essere risolto interamente all'interno del digita sistema.

C'è anche il modo funzionale per gestire questo problema, usando i tipi di opzioni e Maybe , ma non ho familiarità con esso. Preferisco in questo modo perché il codice corretto dovrebbe teoricamente solo bisogno di un carattere aggiunto per compilare correttamente.

    
risposta data 23.07.2017 - 15:31
fonte
19

Esistono molte risposte eccellenti che coprono gli sfortunati sintomi di null , quindi mi piacerebbe presentare un argomento alternativo: Null è un difetto nel sistema dei tipi.

Lo scopo di un sistema di tipi è di garantire che i diversi componenti di un programma "si adattino" correttamente; un programma ben tipizzato non può "uscire dai binari" in un comportamento indefinito.

Considera un ipotetico dialetto di Java, o qualunque sia il tuo linguaggio tipizzato staticamente preferito, in cui puoi assegnare la stringa "Hello, world!" a qualsiasi variabile di qualsiasi tipo:

Foo foo1 = new Foo();  // Legal
Foo foo2 = "Hello, world!"; // Also legal
Foo foo3 = "Bonjour!"; // Not legal - only "Hello, world!" is allowed

E puoi controllare le variabili in questo modo:

if (foo1 != "Hello, world!") {
    bar(foo1);
} else {
    baz();
}

Non c'è nulla di impossibile in questo: qualcuno potrebbe progettare un linguaggio del genere se lo volesse. Il valore speciale non deve essere "Hello, world!" - potrebbe essere stato il numero 42, la tupla (1, 4, 9) o, diciamo, null . Ma perché lo faresti? Una variabile di tipo Foo dovrebbe contenere solo Foo s - questo è l'intero punto del sistema di tipi! null non è un Foo non più di "Hello, world!" . Peggio ancora, null non è un valore di qualsiasi tipo e non c'è niente che tu possa fare con questo!

Il programmatore non può mai essere sicuro che una variabile detenga effettivamente una Foo , e nemmeno il programma; per evitare comportamenti indefiniti, deve controllare le variabili per "Hello, world!" prima di utilizzarle come Foo s. Tieni presente che eseguire il controllo stringa nello snippet precedente non fa propagare il fatto che foo1 è in realtà un Foo - bar probabilmente avrà anche il proprio controllo, solo per sicurezza.

Confrontalo con il tipo Maybe / Option con la corrispondenza del modello:

case maybeFoo of
 |  Just foo => bar(foo)
 |  Nothing => baz()

All'interno della clausola Just foo , sia tu che il programma siete sicuri che la nostra variabile Maybe Foo contenga veramente un valore Foo - tale informazione è propagata lungo la catena di chiamate e bar non ha bisogno fare qualsiasi controllo Poiché Maybe Foo è un tipo distinto da Foo , sei costretto a gestire la possibilità che possa contenere Nothing , quindi non puoi mai farlo alla cieca di un NullPointerException . Puoi ragionare sul tuo programma molto più facilmente e il compilatore può omettere i controlli nulli sapendo che tutte le variabili di tipo Foo contengono effettivamente Foo s. Tutti vincono.

    
risposta data 23.07.2017 - 15:32
fonte
14

(Lanciare il mio cappello sul ring per una vecchia domanda;))

Il problema specifico con null è che interrompe la digitazione statica.

Se ho un Thing t , il compilatore può garantire che posso chiamare t.doSomething() . Bene, UNLESS t è nullo in fase di runtime. Ora tutte le scommesse sono disattivate. Il compilatore ha detto che era OK, ma ho scoperto molto più tardi che t NON è in effetti doSomething() . Quindi, piuttosto che essere in grado di fidarsi del compilatore per individuare gli errori di tipo, devo attendere il runtime per catturarli. Potrei anche usare Python!

Quindi, in un certo senso, null introduce la digitazione dinamica in un sistema tipizzato staticamente, con risultati attesi.

La differenza tra una divisione per zero o un log negativo, ecc. è che quando i = 0, è ancora un int. Il compilatore può comunque garantire il suo tipo. Il problema è che la logica applica male quel valore in un modo che non è permesso dalla logica ... ma se la logica lo fa, è praticamente la definizione di un bug.

(Il compilatore può catturare alcuni di questi problemi, come in Bandierina di espressioni come i = 1 / 0 in fase di compilazione, ma non puoi davvero aspettarti che il compilatore segua una funzione e assicurati che i parametri sono tutti coerenti con la logica della funzione)

Il problema pratico è che fai molto lavoro extra e aggiungi controlli nulli per proteggerti in fase di runtime, ma cosa succede se ne dimentichi uno? Il compilatore ti impedisce di assegnare:

String s = new Integer(1234);

Quindi perché dovrebbe consentire l'assegnazione a un valore (null) che interromperà i de-riferimenti a s ?

Miscelando "nessun valore" con i riferimenti "tipizzati" nel codice, stai mettendo un peso in più ai programmatori. E quando si verificano NullPointerExceptions, rintracciarli può richiedere ancora più tempo. Piuttosto che basarsi sulla digitazione statica per dire "questo è un riferimento a qualcosa che ci si aspetta", stai lasciando che la lingua dica "questo potrebbe essere un riferimento a qualcosa che ci si aspetta. "

    
risposta data 13.03.2014 - 21:46
fonte
13

Un puntatore null è uno strumento

non un nemico. Devi solo usarli correttamente.

Pensa solo un minuto al tempo necessario per trovare e correggere un tipico puntatore non valido , confrontato con l'individuazione e la correzione di un bug di puntatore nullo. È facile controllare un puntatore contro null . È un PITA per verificare se un puntatore non nullo punta o meno a dati validi.

Se non sei ancora convinto, aggiungi una buona dose di multithreading al tuo scenario, poi ripensaci.

Morale della storia: non buttare i bambini con l'acqua. E non incolpare gli strumenti per l'incidente. L'uomo che ha inventato il numero zero molto tempo fa era abbastanza intelligente, dato che quel giorno potevi chiamare il "nulla". Il puntatore null non è così lontano da quello.

EDIT: sebbene il pattern NullObject sembri essere una soluzione migliore rispetto ai riferimenti che potrebbero essere nulli, introduce problemi da solo:

  • Un riferimento che tiene un NullObject dovrebbe (secondo la teoria) non fare nulla quando viene chiamato un metodo. Pertanto, possono essere introdotti errori sottili a causa di riferimenti erroneamente non assegnati, che ora sono garantiti come non nulli (yaha!), Ma eseguono un'azione indesiderata: nulla. Con un NPE è ovvio dove si trova il problema. Con un NullObject che si comporta in qualche modo (ma è sbagliato), introduciamo il rischio di errori rilevati (troppo) tardi. Questo non è per caso simile a un problema di puntatore non valido e ha conseguenze simili: il riferimento punta a qualcosa, che assomiglia a dati validi, ma non lo è, introduce errori nei dati e può essere difficile da rintracciare. Per essere onesti, in questi casi vorrei senza battito di ciglia preferire un NPE che non riesca immediatamente, ora su un errore logico / dati che improvvisamente mi colpisce più avanti. Ricorda lo studio IBM sul costo degli errori in funzione della fase in cui vengono rilevati?

  • La nozione di non fare nulla quando un metodo viene chiamato su NullObject non viene mantenuta, quando viene chiamato un getter di proprietà o una funzione, o quando il metodo restituisce valori tramite out . Diciamo che il valore restituito è un int e decidiamo che "non fare nulla" significa "return 0". Ma come possiamo essere sicuri, che 0 è il giusto "nulla" per essere (non) restituito? Dopo tutto, NullObject non dovrebbe fare nulla, ma quando viene richiesto un valore, deve reagire in qualche modo. Fallire non è un'opzione: usiamo NullObject per prevenire l'NPE, e sicuramente non lo scamberemo con un'altra eccezione (non implementata, dividi per zero, ...), vero? Quindi, come restituisci correttamente nulla quando devi restituire qualcosa?

Non posso aiutarti, ma quando qualcuno prova ad applicare il modello NullObject a tutti i problemi, sembra più come cercare di riparare un errore facendo un altro. È senza dubbio una buona soluzione utile in casi di alcuni , ma sicuramente non è il mirino magico per tutti i casi.

    
risposta data 14.03.2014 - 01:42
fonte
8

I riferimenti null sono un errore perché consentono il codice non sensoriale:

foo = null
foo.bar()

Ci sono alternative, se sfrutti il sistema dei tipi:

Maybe<Foo> foo = null
foo.bar() // error{Maybe<Foo> does not have any bar method}

L'idea generale è di mettere la variabile in una scatola, e l'unica cosa che puoi fare è eliminarla, preferibilmente arruolare l'aiuto del compilatore come proposto per Eiffel.

Haskell lo ha da zero ( Maybe ), in C ++ puoi sfruttare boost::optional<T> ma puoi ancora ottenere un comportamento indefinito ...

    
risposta data 23.07.2017 - 15:32
fonte
8

And what's the alternative?

Tipi opzionali e pattern matching. Dato che non conosco C #, ecco un pezzo di codice in un linguaggio immaginario chiamato Scala #: -)

Customer.GetByLastName("Goodman")    // returns Option[Customer]
match
{
    case Some(customer) =>
    Console.WriteLine(customer.FirstName + " " + customer.LastName + " is awesome!");

    case None =>
    Console.WriteLine("There was no customer named Goodman.  How lame!");
}
    
risposta data 23.07.2017 - 15:33
fonte
6

Il problema con i null è che i linguaggi che consentono loro abbastanza forza di programmazione in modo difensivo contro di esso. Ci vuole un grande sforzo (molto più che cercare di usare i blocchi di sicurezza difensivi) per assicurarci che

  1. gli oggetti che ti aspetti non essere nullo non è mai nullo e
  2. che i tuoi meccanismi difensivi effettivamente affrontare tutti i potenziali NPE in modo efficace.

Quindi, in effetti, i null finiscono per essere una cosa costosa da avere.

    
risposta data 18.10.2010 - 21:44
fonte
4

Il problema in che misura il tuo linguaggio di programmazione tenta di dimostrare la correttezza del tuo programma prima di eseguirlo. In un linguaggio tipizzato staticamente si dimostra di avere i tipi corretti. Passando al valore predefinito di riferimenti non annullabili (con riferimenti nullable opzionali) è possibile eliminare molti dei casi in cui viene passato un valore nullo e non dovrebbe esserlo. La domanda è se lo sforzo extra nella gestione dei riferimenti non annullabili valga il vantaggio in termini di correttezza del programma.

    
risposta data 19.10.2010 - 04:20
fonte
4

Il problema non è tanto nullo, è che non è possibile specificare un tipo di riferimento non nullo in molte lingue moderne.

Ad esempio, il tuo codice potrebbe essere simile a

public void MakeCake(Egg egg, Flour flour, Milk milk)
{
    if (egg == null) { throw ... }
    if (flour == null) { throw ... }
    if (milk == null) { throw ... }

    egg.Crack();
    MixingBowl.Mix(egg, flour, milk);
    // etc
}

// inside Mixing bowl class
public void Mix(Egg egg, Flour flour, Milk milk)
{
    if (egg == null) { throw ... }
    if (flour == null) { throw ... }
    if (milk == null) { throw ... }

    //... etc
}

Quando i riferimenti alle classi vengono passati in giro, la programmazione difensiva ti incoraggia a ricontrollare tutti i parametri per null, anche se li hai appena controllati prima, specialmente quando costruisci unità riutilizzabili sotto test. Lo stesso riferimento potrebbe essere facilmente controllato per 10 volte null attraverso il codebase!

Non sarebbe meglio se potessi avere un normale tipo nullable quando ottieni la cosa, occupati di null allora e lì, e poi passala come un tipo non annullabile a tutte le tue piccole funzioni e classi di helper senza controllo per null tutto il tempo?

Questo è il punto della soluzione Option - non per consentire di attivare gli stati di errore, ma per consentire la progettazione di funzioni che implicitamente non possono accettare argomenti nulli. Se l'implementazione "predefinita" dei tipi sia nullable o non nullable è meno importante della flessibilità di avere entrambi gli strumenti disponibili.

link

Questo è anche elencato come la funzione n. 2 più votata per C # - > link

    
risposta data 23.07.2017 - 15:33
fonte
2

Null è malvagio. Tuttavia, la mancanza di null può essere un male maggiore.

Il problema è che nel mondo reale hai spesso una situazione in cui non hai dati. Il tuo esempio della versione senza null può ancora saltare in aria - o hai commesso un errore logico e hai dimenticato di controllare Goodman o forse Goodman si è sposato tra quando hai controllato e quando l'hai guardata. (Aiuta a valutare la logica se pensi che Moriarty stia guardando ogni bit del tuo codice e cercando di farti inciampare.)

Che cosa fa la ricerca di Goodman quando non è lì? Senza null, devi restituire una sorta di cliente predefinito, e ora stai vendendo cose a quell'impostazione predefinita.

Fondamentalmente, si tratta di capire se è più importante che il codice funzioni, indipendentemente dal fatto che funzioni correttamente. Se un comportamento improprio è preferibile a nessun comportamento, allora non vuoi null. (Per un esempio di come questa potrebbe essere la scelta giusta, si consideri il primo lancio di Ariane V. Un errore non catturato / 0 ha causato un rollio del razzo e l'autodistruzione è esplosa quando si è staccata a causa di questo. stava cercando di calcolare in realtà non ha più avuto alcuno scopo nell'istante in cui il booster è stato acceso - avrebbe reso l'orbita nonostante la routine di restituire i rifiuti.)

Almeno 99 volte su 100 sceglierei di utilizzare null. Però salterò di gioia alla nota per la versione di se stessi.

    
risposta data 11.04.2015 - 01:36
fonte
1

Ottimizza per il caso più comune.

Dovendo controllare per nulla tutto il tempo è noioso - si vuole essere in grado di afferrare l'oggetto Cliente e lavorare con esso.

Nel caso normale, questo dovrebbe funzionare bene - fai il look up, prendi l'oggetto e usalo.

Nel caso eccezionale , dove stai (casualmente) cercando un cliente per nome, non sapendo se quel record / oggetto esiste, avresti bisogno di qualche indicazione che questo non è stato possibile. In questa situazione, la risposta è lanciare un'eccezione RecordNotFound (o lasciare che il provider SQL sotto faccia ciò per te).

Se ti trovi in una situazione in cui non sai se puoi fidarti dei dati in arrivo (il parametro), forse perché è stato inserito da un utente, allora potresti anche fornire il 'TryGetCustomer (nome, fuori cliente) '. Riferimenti incrociati con int.Parse e int.TryParse.

    
risposta data 19.10.2010 - 00:15
fonte
0

Le eccezioni non sono eval!

Sono lì per una ragione. Se il tuo codice funziona male, c'è un modello geniale, chiamato exception , e ti dice che alcune cose sono sbagliate.

Evitando di usare oggetti nulli si nasconde parte di tali eccezioni. Non sto parlando di OP nell'esempio in cui converte l'eccezione del puntatore nullo in un'eccezione ben tipizzata, questo potrebbe effettivamente essere una buona cosa in quanto aumenta la leggibilità. Come mai quando prendi la soluzione Tipo di opzione come sottolineato da @Jonas, stai nascondendo le eccezioni.

Rispetto all'applicazione di produzione, invece di essere lanciati quando si fa clic sul pulsante per selezionare l'opzione vuota, non accade nulla. Invece dell'eccezione del puntatore nullo verrebbe generata e probabilmente otterremmo un report di tale eccezione (come in molte applicazioni di produzione abbiamo un tale meccanismo) e di quello che potremmo effettivamente risolvere.

Rendere il tuo codice a prova di proiettile evitando l'eccezione è una cattiva idea, rendendoti il codice a prova di proiettile risolvendo l'eccezione, beh questo è il percorso che sceglierei.

    
risposta data 13.03.2014 - 09:35
fonte
0

Nulls sono problematici perché devono essere esplicitamente controllati, tuttavia il compilatore non è in grado di avvisarti che hai dimenticato di controllarli. Solo un'analisi statica che richiede molto tempo può dirti questo. Fortunatamente, ci sono molte buone alternative.

Prendi la variabile al di fuori dell'ambito. Troppo spesso, null viene usato come segnaposto quando un programmatore dichiara una variabile troppo presto o la tiene troppo a lungo. L'approccio migliore è semplicemente quello di eliminare la variabile. Non dichiararlo finché non hai un valore valido da inserire lì. Questa non è una restrizione così difficile come potresti pensare, e rende il tuo codice molto più pulito.

Usa il modello di oggetto nullo. A volte, un valore mancante è uno stato valido per il tuo sistema. Il modello di oggetto nullo è una buona soluzione qui. Evita la necessità di controlli espliciti costanti, ma consente comunque di rappresentare uno stato nullo. Non è "papering over an error condition", come sostengono alcune persone, perché in questo caso uno stato nullo è uno stato semantico valido. Detto questo, non si dovrebbe usare questo modello quando uno stato nullo non è uno stato semantico valido. Devi solo prendere la tua variabile fuori dal campo di applicazione.

Usa un'opzione / Forse. Prima di tutto, questo ti consente di avvisare il compilatore che è necessario verificare la presenza di un valore mancante, ma fa più che sostituire un tipo di controllo esplicito con un altro. Usando Options , puoi concatenare il tuo codice e continuare come se il tuo valore esistesse, senza dover effettivamente controllare fino all'ultimo minuto assoluto. In Scala, il tuo codice di esempio sarebbe simile a:

val customer = customerDb getByLastName "Goodman"
val awesomeMessage =
  customer map (c => s"${c.firstName} ${c.lastName} is awesome!")
val notFoundMessage = "There was no customer named Goodman.  How lame!"
println(awesomeMessage getOrElse notFoundMessage)

Nella seconda riga, dove stiamo costruendo il fantastico messaggio, non facciamo un controllo esplicito se il cliente è stato trovato, e la bellezza è non abbiamo bisogno di . Non è fino all'ultima riga, che potrebbe essere molte righe dopo, o anche in un modulo diverso, in cui ci occupiamo esplicitamente di cosa succede se Option è None . Non solo, se ci fossimo dimenticati di farlo, non avrebbe controllato il tipo. Anche allora, è fatto in modo molto naturale, senza un'esplicita dichiarazione di if .

Confrontalo con un null , dove digita i controlli bene, ma dove devi fare un controllo esplicito in ogni fase o l'intera applicazione esplode in fase di runtime, se sei fortunato durante un uso caso la tua unità test esercizio. Non vale la pena.

    
risposta data 18.05.2016 - 20:50
fonte
0

Il problema centrale di NULL è che rende il sistema inaffidabile. Nel 1980 Tony Hoare nel saggio dedicato al suo Premio Turing scrisse:

And so, the best of my advice to the originators and designers of ADA has been ignored. …. Do not allow this language in its present state to be used in applications where reliability is critical, i.e., nuclear power stations, cruise missiles, early warning systems, antiballistic missile defense systems. The next rocket to go astray as a result of a programming language error may not be an exploratory space rocket on a harmless trip to Venus: It may be a nuclear warhead exploding over one of our own cities. An unreliable programming language generating unreliable programs constitutes a far greater risk to our environment and to our society than unsafe cars, toxic pesticides, or accidents at nuclear power stations. Be vigilant to reduce the risk, not to increase it.

Il linguaggio ADA è cambiato molto da allora, tuttavia esistono ancora problemi in Java, C # e in molti altri linguaggi popolari.

È dovere dello sviluppatore creare contratti tra un cliente e un fornitore. Ad esempio, in C #, come in Java, puoi utilizzare Generics per ridurre al minimo l'impatto del riferimento Null creando readonly NullableClass<T> (due Opzioni):

class NullableClass<T>
{
     public HasValue {get;}
     public T Value {get;}
}

e quindi usarlo come

NullableClass<Customer> customer = dbRepository.GetCustomer('Mr. Smith');
if(customer.HasValue){
  // one logic with customer.Value
}else{
  // another logic
} 

o usa due opzioni di stile con i metodi di estensione C #:

customer.Do(
      // code with normal behaviour
      ,
      // what to do in case of null
) 

La differenza è significativa. Come cliente di un metodo sai cosa aspettarti. Una squadra può avere la regola:

Se una classe non è di tipo NullableClass, allora l'istanza non deve essere null .

Il team può rafforzare questa idea utilizzando Design by Contract e il controllo statico al momento della compilazione, ad es. con precondizione:

function SaveCustomer([NotNullAttribute]Customer customer){
     // there is no need to check whether customer is null 
     // it is a client problem, not this supplier
}

o per una stringa

function GetCustomer([NotNullAndNotEmptyAttribute]String customerName){
     // there is no need to check whether customerName is null or empty 
     // it is a client problem, not this supplier
}

Questo approccio può aumentare drasticamente l' affidabilità dell'applicazione e la qualità del software. Design by Contract è un caso di logica Hoare , che è stata popolata da Bertrand Meyer nel suo famoso libro di costruzione di software orientato agli oggetti e Eiffel language nel 1988, ma non è utilizzato in modo non valido nella moderna produzione di software.

    
risposta data 23.07.2017 - 15:34
fonte
-1

Fondamentalmente l'idea centrale è che i problemi di riferimento del puntatore nullo dovrebbero essere catturati in fase di compilazione invece del tempo di esecuzione. Quindi se stai scrivendo una funzione che prende un oggetto e non vuoi che qualcuno la chiami con riferimenti null allora il sistema di tipo dovrebbe permetterti di specificare quel requisito in modo che il compilatore possa usarlo per garantire che la chiamata alla tua funzione non sarebbe mai compilare se il chiamante non soddisfa il tuo requisito. Questo può essere fatto in molti modi, ad esempio, decorando i tuoi tipi o usando i tipi di opzione (chiamati anche tipi nullable in alcune lingue) ma ovviamente è molto più lavoro per i progettisti di compilatori.

Penso sia comprensibile il motivo per cui negli anni '60 e '70, il progettista di compilatori non è andato all-in per implementare questa idea perché le macchine erano limitate in termini di risorse, i compilatori dovevano essere mantenuti relativamente semplici e nessuno sapeva davvero quanto sarebbe stato male scopre 40 anni sulla linea.

    
risposta data 06.01.2016 - 00:01
fonte
-1

Ho una semplice obiezione contro null :

Rompe completamente la semantica del tuo codice introducendo l'ambiguità .

Spesso ti aspetti che il risultato sia di un certo tipo. Questo è semanticamente valido. Supponiamo che tu abbia chiesto al database un utente con un determinato id , che il resut sia di un certo tipo (= user ). Ma cosa succede se non ci sono utenti di quell'id? Una (cattiva) soluzione è: aggiungere un valore null . Quindi il risultato è ambiguo : o è user o è null . Ma null non è il tipo previsto. Ed è qui che inizia il codice olfattivo .

Per evitare null , rendi il tuo codice semanticamente chiaro.

Ci sono sempre modi per aggirare null : refactoring per le raccolte, Null-objects , optionals ecc.

    
risposta data 23.04.2016 - 12:39
fonte
-2

Se sarà semanticamente possibile che una posizione di memoria di riferimento o tipo di puntatore a cui accedere prima che il codice sia stato eseguito per calcolare un valore per esso, non c'è alcuna scelta perfetta su cosa dovrebbe accadere. Avendo posizioni predefinite come riferimenti a puntatori nulli che possono essere liberamente letti e copiati, ma che si ripercuotono in caso di dereferenziazione o indicizzazione, è una possibile scelta. Altre scelte includono:

  1. Le posizioni predefinite sono impostate su un puntatore nullo, ma non consentono al codice di esaminarle (o persino di determinarne la nullità) senza arrestarsi in modo anomalo. Possibilmente fornire un mezzo esplicito per impostare un puntatore su null.
  2. Le posizioni predefinite sono impostate su un puntatore nullo e si arrestano in modo anomalo se viene effettuato un tentativo di leggerne una, ma forniscono un metodo non di blocco per verificare se un puntatore contiene un valore nullo. Fornire anche un mezzo esplicativo per impostare un puntatore su null.
  3. Le posizioni predefinite sono un puntatore a un'istanza predefinita fornita dal compilatore specifica del tipo indicato.
  4. Le posizioni predefinite sono impostate su un puntatore a un'istanza predefinita fornita dal programma specifica del tipo indicato.
  5. Gli accessi al puntatore nullo (non solo le operazioni di dereferenziazione) chiamano alcune routine fornite dal programma per fornire un'istanza.
  6. Progettare la semantica del linguaggio in modo tale che non esistano raccolte di posizioni di memorizzazione, ad eccezione di un inaccessibile compilatore temporaneo, fino al momento in cui le routine di inizializzazione sono state eseguite su tutti i membri (qualcosa come un costruttore di array dovrebbe essere fornito con un istanza predefinita, una funzione che restituisce un'istanza o eventualmente una coppia di funzioni: una per restituire un'istanza e una che verrebbe chiamata su istanze create in precedenza se si verifica un'eccezione durante la costruzione dell'array).

L'ultima scelta potrebbe avere un certo appeal, specialmente se una lingua includesse sia i tipi nullable che quelli non annullabili (si potrebbero chiamare costruttori di array speciali per qualsiasi tipo, ma si richiede solo di chiamarli quando creano matrici di valori non nullable tipi), ma probabilmente non sarebbe stato possibile nel periodo in cui sono stati inventati i puntatori nulli. Tra le altre scelte, nessuna sembra più accattivante che consentire di copiare i puntatori nulli ma non dereferenziati o indicizzati. L'approccio n. 4 potrebbe essere conveniente avere come opzione e dovrebbe essere abbastanza economico da implementare, ma non dovrebbe certamente essere l'unica opzione. Richiedere che i puntatori per impostazione predefinita puntino ad un particolare oggetto valido è molto peggio che avere i puntatori predefiniti su un valore nullo che può essere letto o copiato ma non dereferenziato o indicizzato.

    
risposta data 13.07.2012 - 18:22
fonte
-2

Sì, 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:47
fonte

Leggi altre domande sui tag