Perchè i riferimenti null sono evitati mentre il lancio delle eccezioni è considerato ok?

20

Non capisco del tutto il bashing costante di riferimenti null da parte di alcuni membri del linguaggio di programmazione. Cosa c'è di male in loro? Se richiedo l'accesso in lettura a un file che non esiste, sono perfettamente felice di ottenere un'eccezione o un riferimento null e tuttavia le eccezioni sono considerate valide ma i riferimenti null sono considerati non validi. Qual è il ragionamento dietro questo?

    
posta davidk01 23.05.2011 - 21:19
fonte

13 risposte

24

I riferimenti nulli non sono "evitati" più delle eccezioni, almeno da chiunque io abbia mai conosciuto o letto. Penso che tu fraintenda la saggezza convenzionale.

Ciò che è male è un tentativo di accedere a un riferimento null (o dereferenziare un puntatore nullo, ecc.). Questo è male perché indica sempre un bug; non avresti mai fatto qualcosa del genere di proposito, e se lo stai facendo di proposito, allora è anche peggio, perché rende impossibile distinguere il comportamento atteso dal comportamento buggy.

Ci sono alcuni gruppi marginali là fuori che odiano davvero il concetto di nullità per qualche ragione, ma come sottolinea Ed , se non hai null o nil allora dovrai solo sostituirlo con qualcos'altro, che potrebbe portare a qualcosa di peggio di un incidente (come il danneggiamento dei dati).

Molte strutture, infatti, abbracciano entrambi i concetti; per esempio, in .NET, uno schema frequente che vedrai è una coppia di metodi, uno preceduto dalla parola Try (come TryGetValue ). Nel caso Try , il riferimento viene impostato sul valore predefinito (in genere null ) e nell'altro caso viene generata un'eccezione. Non c'è niente di sbagliato in entrambi gli approcci; entrambi sono usati frequentemente in ambienti che li supportano.

Dipende tutto dalla semantica. Se null è un valore di ritorno valido, come nel caso generale della ricerca di una raccolta, quindi restituisce null . D'altra parte, se non è un valore di ritorno valido - per esempio, cercando un record usando una chiave primaria proveniente dal proprio database - allora restituire null sarebbe una cattiva idea perché il chiamante non se lo aspetta e probabilmente non lo controllerà.

È davvero molto semplice capire quale semantica usare: Ha senso che il risultato di una funzione non sia definito? In tal caso, puoi restituire un riferimento null. In caso contrario, lancia un'eccezione.

    
risposta data 23.05.2011 - 22:20
fonte
11

La grande differenza è che se si omette il codice per gestire i NULL, il codice continuerà probabilmente a bloccarsi in una fase successiva con un messaggio di errore non correlato, dove, come per le eccezioni, l'eccezione verrà sollevata nel punto iniziale di fallimento (apertura di un file per la lettura nel tuo esempio).

    
risposta data 23.05.2011 - 21:30
fonte
7

Perché i valori nulli non sono una parte necessaria di un linguaggio di programmazione e sono una fonte coerente di bug. Come dici tu, l'apertura di un file può causare un errore, che potrebbe essere comunicato come valore di ritorno nullo o tramite un'eccezione. Se non sono consentiti valori nulli, esiste un modo coerente e singolare di comunicare errori.

Inoltre, questo non è il problema più comune con null. La maggior parte delle persone ricorda di verificare la presenza di null dopo aver chiamato una funzione che potrebbe restituirla. Il problema si manifesta molto più nel tuo progetto consentendo alle variabili di essere nulle in vari punti nell'esecuzione del tuo programma. Puoi progettare il tuo codice in modo tale che i valori nulli non siano mai consentiti, ma se non fosse consentito null a livello di lingua, nulla di tutto ciò sarebbe necessario.

Tuttavia, in pratica, avresti ancora bisogno di un modo per indicare se una variabile è o non è inizializzata. Avresti quindi una forma di bug in cui il tuo programma non si arresta in modo anomalo, ma continua invece usando un valore predefinito eventualmente non valido. Onestamente non so quale sia il migliore. Per i miei soldi mi piace andare in crash presto e spesso.

    
risposta data 23.05.2011 - 21:53
fonte
5

Tony Hoare, che ha creato l'idea di un riferimento null in primo luogo, lo chiama il suo errore da un milione di dollari .

Il problema non riguarda i riferimenti null di per sé, ma la mancanza di un corretto controllo dei tipi nella maggior parte (altrimenti) digita i linguaggi sicuri.

Questa mancanza di supporto, dal linguaggio, significa che i bug "null-bug" potrebbero essere in agguato nel programma per molto tempo prima di essere rilevati. Questa è la natura dei bug, ovviamente, ma i "null bug" ora sono noti per essere evitabili.

Questo problema è particolarmente presente in C o C ++ (ad esempio) a causa dell'errore "difficile" che causa (un arresto anomalo del programma, immediato, senza un recupero elegante).

In altre lingue, c'è sempre la domanda su come gestirli.

In Java o C # si otterrebbe un'eccezione se si tenta di invocare un metodo su un riferimento null e potrebbe essere corretto. E quindi la maggior parte dei programmatori Java o C # sono abituati a questo e non capiscono perché si vorrebbe fare altrimenti (e ridere di C ++).

In Haskell, devi esplicitamente fornire un'azione per il caso nullo, e quindi i programmatori di Haskell si lamentano dei loro colleghi, perché hanno capito bene (giusto?).

È, in realtà, il vecchio dibattito sui codici di errore / eccezione, ma questa volta con un Valore Sentinel al posto del codice di errore.

Come sempre, ciò che è più appropriato dipende molto dalla situazione e dalla semantica che stai cercando.

    
risposta data 24.05.2011 - 20:19
fonte
2

Non puoi allegare un messaggio di errore leggibile da un utente a un puntatore nullo.

(Puoi comunque lasciare un messaggio di errore in un file di registro.)

In alcuni linguaggi / ambienti che consentono l'aritmetica del puntatore, se uno degli argomenti del puntatore è nullo ed è consentito nel calcolo, il risultato sarebbe un non valido , non-null puntatore. (*) Più potenza per te.

(*) Questo accade molto nella programmazione COM , dove se provi a chiamare in un'interfaccia metodo ma il puntatore all'interfaccia è nullo, risulterebbe in una chiamata a un indirizzo non valido che è quasi, ma non del tutto, esattamente diverso da zero.

    
risposta data 24.05.2011 - 06:58
fonte
2

Restituire NULL (o zero numerico o booleano falso) per segnalare che un errore è sbagliato sia dal punto di vista tecnico che concettuale.

Tecnicamente, stai gravando sul programmatore controllando il valore restituito subito , nel punto esatto in cui viene restituito. Se si aprono venti file in una riga e la segnalazione di errore viene eseguita restituendo NULL, il codice che consuma deve controllare ogni file letto singolarmente e uscire da eventuali loop e costrutti simili. Questa è una ricetta perfetta per il codice ingombra. Se, tuttavia, si sceglie di segnalare l'errore generando un'eccezione, il codice che consuma può scegliere di gestire immediatamente l'eccezione, oppure lasciarlo scoppiare su tutti i livelli appropriati, anche attraverso le chiamate di funzione. Questo rende molto più pulito il codice.

Concettualmente, se apri un file e qualcosa va storto, restituire un valore (anche NULL) è sbagliato. Non hai nulla da restituire, perché la tua operazione non è finita. Restituire NULL è l'equivalente concettuale di "Ho letto con successo il file, ed ecco cosa contiene - niente". Se questo è ciò che vuoi esprimere (ovvero se NULL ha senso come risultato effettivo per l'operazione in questione), allora restituisci NULL, ma se vuoi segnalare un errore, usa le eccezioni.

Storicamente, gli errori sono stati segnalati in questo modo perché linguaggi di programmazione come C non hanno una gestione delle eccezioni integrata nel linguaggio, e il modo consigliato (usando salti lunghi) è un po 'peloso e un po' contro-intuitivo.

C'è anche un lato di manutenzione del problema: con le eccezioni, devi scrivere un codice extra per gestire l'errore; se non lo fai, il programma fallirà presto e duramente (che è buono). Se si restituisce NULL per segnalare errori, il comportamento predefinito per il programma è di ignorare l'errore e proseguire fino a quando non porta ad altri problemi lungo la strada: dati corrotti, segfaults, NullReferenceExceptions, a seconda della lingua. Per segnalare l'errore in anticipo e ad alta voce, devi scrivere un codice extra, e indovina un po ': questa è la parte che viene lasciata fuori quando hai una scadenza ristretta.

    
risposta data 24.05.2011 - 12:09
fonte
1

Come già sottolineato, molte lingue non convertono un dereferenziamento di un puntatore nullo in un'eccezione catchable. Fare questo è un trucco relativamente moderno. Quando il problema del puntatore nullo è stato riconosciuto per la prima volta, le eccezioni non erano ancora state inventate.

Se consenti ai puntatori nulli un caso valido, questo è un caso speciale. Hai bisogno di una logica di gestione del caso speciale, spesso in molti posti diversi. Questa è una complessità extra.

Che si riferisca a puntatori potenzialmente nulli o meno, se non usi tiri di eccezioni per gestire casi eccezionali, devi trattare quei casi eccezionali in un altro modo. In genere, ogni chiamata di funzione deve avere controlli per quei casi eccezionali, sia per impedire che la chiamata alla funzione venga chiamata in modo inappropriato, sia per rilevare il caso di errore quando la funzione viene chiusa. Questa è una complessità extra che può essere evitata usando le eccezioni.

Più complessità di solito significa più errori.

Le alternative all'utilizzo di puntatori nulli nelle strutture dati (ad esempio per contrassegnare l'inizio / la fine di un elenco collegato) includono l'uso di elementi sentinella. Questi possono dare la stessa funzionalità con molta meno complessità. Tuttavia, ci possono essere altri modi per gestire la complessità. Un modo è quello di avvolgere il puntatore potenzialmente nullo in una classe di puntatore intelligente in modo che i controlli null siano necessari solo in un unico punto.

Che cosa fare del puntatore nullo quando viene rilevato? Se non è possibile creare la gestione di casi eccezionali, è sempre possibile generare un'eccezione e delegare effettivamente la gestione di casi speciali al chiamante. Ed è esattamente ciò che alcuni linguaggi fanno di default quando si denota un puntatore nullo.

    
risposta data 24.05.2011 - 05:50
fonte
1

Specifico per C ++, ma qui sono riferimenti null perché la semantica nulla in C ++ è associata ai tipi di puntatore. È abbastanza ragionevole che una funzione di apertura di file fallisca e restituisca un puntatore nullo; infatti la funzione fopen() fa esattamente questo.

    
risposta data 24.05.2011 - 13:58
fonte
1

Dipende dalla lingua.

Ad esempio, Objective-C consente di inviare un messaggio a un oggetto null (nil) senza problemi. Anche una chiamata a nil restituisce zero ed è considerata una funzionalità linguistica.

Personalmente mi piace dal momento che puoi fare affidamento su questo comportamento ed evitare tutti quei complicati costrutti di% co_de nidificati.

Ad esempio:

if (myObject != nil && [myObject doSomething])
{
    ...
}

Può essere abbreviato in:

if ([myObject doSomething])
{
    ...
}

In breve, rende il tuo codice più leggibile.

    
risposta data 24.05.2011 - 16:06
fonte
0

Non mi frega niente se si restituisce un valore nullo o si genera un'eccezione, purché lo si documenta.

    
risposta data 24.05.2011 - 05:07
fonte
-1

Un riferimento Null si verifica di solito perché mancava qualcosa nella logica del programma, ovvero: si arrivava a una linea di codice senza passare attraverso il set-up richiesto per quel blocco di codice.

D'altra parte, se si lancia un'eccezione per qualcosa significa che si è riconosciuto che una situazione particolare potrebbe verificarsi nel normale funzionamento del programma e che viene gestita.

    
risposta data 23.05.2011 - 23:02
fonte
-1

I riferimenti nulli sono spesso molto utili: ad esempio, un elemento in un elenco collegato può avere un successore o nessun successore. Usare null per "nessun successore" è perfettamente naturale. Oppure, una persona può avere un coniuge o meno - usare null per "la persona non ha coniuge" è perfettamente naturale, molto più naturale di avere un "non ha coniuge" - un valore speciale a cui il membro di Person.Spouse potrebbe fare riferimento.

Ma: molti valori sono non facoltativi. In un tipico programma OOP, direi che più della metà dei riferimenti non può essere null dopo l'inizializzazione, altrimenti il programma fallirà. Altrimenti il codice dovrebbe essere pieno di controlli if (x != null) . Quindi, perché il riferimento ogni può essere annullabile per impostazione predefinita? In realtà dovrebbe essere il contrario: le variabili dovrebbero essere non annullabili per impostazione predefinita e dovresti dire esplicitamente "oh, e anche questo valore può essere null ".

    
risposta data 24.05.2011 - 00:01
fonte
-1

La tua domanda è formulata in modo confuso. Intendi un riferimento null eccezione (che in realtà è causato da un tentativo di de riferimento null)? Il chiaro motivo per non volere quel tipo di eccezione è che non ti dà alcuna informazione su cosa è andato storto, o anche quando - il valore potrebbe essere stato impostato su null in qualsiasi punto del programma. Scrivi "Se richiedo l'accesso in lettura a un file che non esiste, sono perfettamente felice di ottenere un'eccezione o un riferimento null", ma non dovresti essere perfettamente felice di ottenere qualcosa che non dia indicazioni sulla causa . In nessuna parte della stringa "è stato effettuato un tentativo di dereferenziamento null" si fa menzione della lettura o di file non esistenti. Forse vuoi dire che saresti perfettamente felice di ottenere null come valore di ritorno da una chiamata di lettura - è una questione completamente diversa, ma non ti dà ancora nessuna informazione sul perché la lettura non è andata a buon fine; è molto meglio ricevere o un oggetto di errore che dettagli la natura dell'errore, o di avere un oggetto di errore generato (notare la distinzione e l'eventuale confusione tra un'eccezione, che è un oggetto e un'eccezione, che è un evento che si traduce nel lancio di un tale oggetto).

    
risposta data 24.05.2011 - 00:42
fonte

Leggi altre domande sui tag