2009-03-13 16 views
7

Continuo a sentire le persone parlare di come tipi di riferimento non annullabili possano risolvere così tanti bug e rendere la programmazione molto più semplice. Anche il creatore di null lo chiama il suo billion dollar mistake e Spec# ha introdotto tipi non annullabili per ovviare a questo problema.Discussione sui tipi non annullabili

MODIFICA: Ignora il mio commento su SpeC#. Ho frainteso come funziona.

EDIT 2: devo essere parlando con le persone sbagliate, speravo davvero che qualcuno discutere con :-)


Quindi direi, essendo in minoranza, che ho torto, ma non riesco a capire perché questo dibattito abbia qualche merito. Vedo null come strumento di individuazione dei bug. Considera quanto segue:

class Class { ... } 

void main() { 
    Class c = nullptr; 
    // ... ... ... code ... 
    for(int i = 0; i < c.count; ++i) { ... } 
} 

BAM! Violazione di accesso. Qualcuno ha dimenticato di inizializzare c.


Ora considerare questo:

class Class { ... } 

void main() { 
    Class c = new Class(); // set to new Class() by default 
    // ... ... ... code ... 
    for(int i = 0; i < c.count; ++i) { ... } 
} 

Ops. Il ciclo viene saltato silenziosamente. Potrebbe volerci un po 'per rintracciare il problema.


Se la classe è vuota, il codice fallirà comunque. Perché il sistema non ti dice (anche se un po 'bruscamente) invece di doverlo scoprire da solo?

+0

È bello vedere gli altri divertirsi, sono ancora a scuola, quindi presumo che ci sia qualcosa che mi manca. –

+1

Esistono altri metodi di principio per gestire "nessun valore". NULL esclude i tipi primitivi, come int. È meglio per un sistema di tipi rappresentare la mancanza di valore in modo coerente su tutti i tipi, invece che solo implicitamente per i riferimenti. Vedi i tipi di "Opzione" di Haskell "Forse" e ML/OCaml/F # per vedere come dovrebbe essere fatto. – MichaelGG

+0

possibile duplicato di [Migliore spiegazione per le lingue senza null] (http://stackoverflow.com/questions/3989264/best-explanation-for-languages-without-null) – nawfal

risposta

-2

Mi piace l'utilizzo di NULL. È passato un po 'di tempo da quando ho lavorato in C++, ma ha reso molto facile trovare i miei problemi relativi ai valori di ritorno e ai riferimenti e correggerli.

+0

Manca il punto. –

2

Ammetto che non ho letto molto su SpeC#, ma avevo capito che il NonNullable era essenzialmente un attributo che ponevi su un parametro, non necessariamente su una dichiarazione di variabile; Trasforma il tuo esempio in qualcosa di simile:

class Class { ... } 

void DoSomething(Class c) 
{ 
    if (c == null) return; 
    for(int i = 0; i < c.count; ++i) { ... } 
} 

void main() { 
    Class c = nullptr; 
    // ... ... ... code ... 
    DoSomething(c); 
} 

Con SpeC#, che esegui il markup doSomething a dire "il parametro c non può essere nullo". Mi sembra una buona caratteristica, perché significa che non ho bisogno della prima riga nel metodo DoSomething() (che è una linea facile da dimenticare, e del tutto priva di significato nel contesto di DoSomething()).

6

Non capisco il tuo esempio. Se il tuo "= new Class()" è solo un segnaposto al posto di non avere nulla, allora è (ai miei occhi) ovviamente un bug. Se non lo è, il vero problema è che "..." non ha impostato correttamente il suo contenuto, che è esattamente lo stesso in entrambi i casi.

Un'eccezione che mostra che hai dimenticato di inizializzare c ti dirà a che punto non è inizializzato, ma non dove avrebbe dovuto essere inizializzato. Allo stesso modo, un loop mancato (implicitamente) ti dirà dove è necessario avere un conteggio diverso da zero, ma non quello che avrebbe dovuto essere fatto o dove. Non vedo nessuno dei due più facile sul programmatore.

Non penso che il punto di "nessun nulla" sia semplicemente fare una ricerca e sostituzione di testo e renderli tutti in istanze vuote. Ovviamente è inutile.Il punto è strutturare il codice in modo che le variabili non siano mai in uno stato in cui indicano valori inutili/non corretti, di cui NULL è semplicemente il più comune.

+0

Quindi, se un linguaggio implementa tipi non annullibili, cosa significa un'istruzione come "Classe c;" impostare c a? (Sono totalmente d'accordo con i tuoi commenti a proposito) – zildjohn01

+2

Il mio primo pensiero: non dovrebbe essere permesso. Logicamente non ha senso dire "creare uno slot che deve memorizzare un Foo (ma lasciarlo vuoto)".Questo probabilmente richiede tutto-è-un-espressione (come Lisp o Ruby) - Non so come funzionerebbe se si dovessero eseguire istruzioni sequenziali per fare assegnamento. – Ken

+1

Guardando indietro: pensa SQL. Se si dispone di una colonna "NOT NULL" e si tenta di inserire un record senza specificare un valore non null per esso, si solleva semplicemente un errore. – Ken

0

Come vedo, ci sono due aree in cui viene utilizzato null.

Il primo è l'assenza di un valore. Ad esempio, un booleano può essere vero o falso oppure l'utente non ha ancora scelto un'impostazione, quindi null. Questo è utile e una buona cosa, ma forse è stato implementato in modo errato in origine e ora c'è un tentativo di formalizzare tale uso. (Dovrebbe esserci un secondo booleano per mantenere lo stato set/unset o null come parte di una logica a tre stati?)

Il secondo è nel senso di puntatore nullo. Questo è più spesso di una situazione di errore del programma, cioè. un'eccezione. Non è uno stato previsto, c'è un errore di programma. Questo dovrebbe essere sotto l'ombrello di eccezioni formali, come implementato nelle lingue moderne. Cioè, una NullException viene catturata tramite un blocco try/catch.

Quindi, a quale di questi sei interessato?

+0

# 2. – zildjohn01

2

L'idea dei tipi non nulli consiste nel consentire al compilatore piuttosto che al client di trovare i bug. Supponiamo di aggiungere alla tua lingua due specificatori di tipo @nullable (potrebbe essere null) e @nonnull (mai null) (sto usando la sintassi dell'annotazione Java).

Quando si definisce una funzione, si annotano i suoi argomenti. Per esempio, il seguente codice verrà compilato

int f(@nullable Foo foo) { 
    if (foo == null) 
    return 0; 
    return foo.size(); 
}

Anche se foo può essere nullo all'ingresso, il flusso delle garanzie di controllo che, quando si chiama foo.size(), foo è non nullo.

Ma se si rimuove il controllo per null, si otterrà un errore in fase di compilazione.

Di seguito vi compilare anche perché foo è non nullo all'ingresso:

int g(@nonnull Foo foo) { 
    return foo.size(); // OK 
}

Tuttavia, non sarà in grado di chiamare g con un puntatore annullabile:

@nullable Foo foo; 
g(foo); // compiler error!

Il compilatore fa analisi del flusso per ogni funzione, in modo che possa rilevare quando @nullable diventa @nonnull (ad esempio, all'interno di un'istruzione if che controlla null). Accetterà anche una definizione verosimile @null, a condizione che venga immediatamente inizializzata.

@nonnull Foo foo = new Foo();

C'è molto di più su questo argomento in my blog.

0

Attualmente sto lavorando su questo argomento in C#. .NET ha Nullable per i tipi di valore, ma la funzione inversa non esiste per i tipi di riferimento.

Ho creato NotNullable per i tipi di riferimento e spostato il problema da if (non più controlli per null) al dominio datatype. Questo rende l'applicazione per generare eccezioni in runtime e non in fase di compilazione.

12

E 'un po' strano che la risposta scritta "risposta" in questa discussione in realtà mette in evidenza il problema con null, in primo luogo, vale a dire:

Ho anche scoperto che la maggior parte dei miei NULL errori puntatore ruotano attorno alle funzioni da dimenticare per controllare il ritorno delle funzioni di string.h, dove NULL viene utilizzato come un indicatore.

Non sarebbe bello se il compilatore riuscisse a rilevare questi tipi di errori in fase di compilazione anziché in fase di esecuzione?

Se si è utilizzato un linguaggio simile a ML (SML, OCaml, SML e F # in una certa misura) o Haskell, i tipi di riferimento non sono annullabili. Invece, rappresenti un valore "nullo" avvolgendolo in un tipo di opzione. In questo modo, si modifica effettivamente il tipo di ritorno di una funzione se può restituire null come valore legale. Quindi, diciamo che ho voluto tirare un utente dal database:

let findUser username = 
    let recordset = executeQuery("select * from users where username = @username") 
    if recordset.getCount() > 0 then 
     let user = initUser(recordset) 
     Some(user) 
    else 
     None 

utente Trova ha il tipo val findUser : string -> user option, in modo che il tipo di ritorno della funzione in realtà ti dice che può restituire un valore nullo. Per consumare il codice, è necessario gestire sia i Alcuni e None casi:

match findUser "Juliet Thunderwitch" with 
| Some x -> print_endline "Juliet exists in database" 
| None -> print_endline "Juliet not in database" 

Se non si gestisce entrambi i casi, il codice non sarà nemmeno la compilazione. Quindi il sistema dei tipi garantisce che non otterrai mai un'eccezione di riferimento null e garantisce che tu gestisca sempre i valori null. E se una funzione restituisce user, è garantita un'istanza reale di un oggetto. Grandiosità.

ora vediamo il problema nel codice di esempio del PO:

class Class { ... } 

void main() { 
    Class c = new Class(); // set to new Class() by default 
    // ... ... ... code ... 
    for(int i = 0; i < c.count; ++i) { ... } 
} 

inizializzato e gli oggetti non inizializzati avere lo stesso tipo di dati, non si può dire la differenza tra loro. Occasionalmente, lo null object pattern può essere utile, ma il codice sopra dimostra che il compilatore non ha modo di determinare se stai usando i tuoi tipi correttamente.

0

I tipi non annullabili hanno più senso per me quando si tratta di oggetti di dominio. Quando si mappano le tabelle del database agli oggetti e si hanno colonne non annullabili. Supponiamo che tu abbia una tabella chiamata User e che abbia userid varchar (20) non annullabile;

Sarebbe così conveniente avere una classe utente con campo stringa UserId che non è annullabile. Puoi ridurre alcuni bug al momento della compilazione.