Se i null sono cattivi, cosa dovrebbe essere usato quando un valore può essere significativamente assente?


26

Questa è una delle regole che si ripetono ripetutamente e che mi sconcerta.

I nullità sono malvagi e dovrebbero essere evitati quando possibile .

Ma, ma - per mia ingenuità, fammi urlare - a volte un valore può essere significativamente assente!

Per favore, lasciatemi fare una domanda su un esempio che proviene da questo orribile codice anti-pattern su cui sto lavorando al momento . Questo è, fondamentalmente, un gioco a turni multiplayer basato sul web, in cui i turni di entrambi i giocatori vengono eseguiti simultaneamente (come in Pokemon e in contrapposizione a Scacchi).

Dopo ogni giro il server trasmette un elenco di aggiornamenti al codice JS lato client. La classe di aggiornamento:

public class GameUpdate
{
    public Player player;
    // other stuff
}

Questa classe è serializzata su JSON e inviata ai giocatori collegati.

Alla maggior parte degli aggiornamenti è naturalmente associato un giocatore: dopo tutto, è necessario sapere quale giocatore ha effettuato quale mossa in questo turno. Tuttavia, alcuni aggiornamenti non possono avere un giocatore associato in modo significativo con loro. Esempio: il gioco è stato forzato perché il limite di turno senza azioni è stato superato. Per tali aggiornamenti, penso, sia significativo che il giocatore sia annullato.

Certo, potrei "riparare" questo codice utilizzando l'ereditarietà:

public class GameUpdate
{
    // stuff
}

public class GamePlayerUpdate : GameUpdate
{
    public Player player;
    // other stuff
}

Tuttavia, non riesco a vedere come questo sia di alcun miglioramento, per due motivi:

  • Il codice JS ora riceverà semplicemente un oggetto senza Player come proprietà definita, che è lo stesso di se fosse null poiché entrambi i casi richiedono di verificare se il valore è presente o assente;
  • Se un altro campo nullable fosse aggiunto alla classe GameUpdate, dovrei essere in grado di utilizzare l'ereditarietà multipla per continuare con questo desing - ma MI è un male a sé stante (secondo i programmatori più esperti) e, cosa più importante, C # non lo fa ce l'ho quindi non posso usarlo.

Ho la sensazione che questo pezzo di questo codice sia uno dei tanti in cui un programmatore esperto e bravo urlerebbe inorridito. Allo stesso tempo, non riesco a vedere come, in questo posto, questo null sia dannoso per qualcosa e cosa dovrebbe essere fatto invece.

Potresti spiegarmi questo problema?


2
Questa domanda è specifica per JavaScript? Molte lingue hanno un tipo opzionale per questo scopo, ma non so se js ne abbia uno (anche se potresti farne uno) o se funzionerebbe con json (anche se questa è una parte di controlli nulli cattivi piuttosto che tutto il tuo codice
Richard Tingle

9
Questa regola è semplicemente falsa. Sono un po 'stupito che qualcuno si iscriva a questa nozione. Ci sono alcune buone alternative a null in alcuni casi. null è davvero buono per rappresentare un valore mancante. Non lasciare che regole Internet casuali come questa ti impediscano di fidarti del tuo giudizio motivato.
usr

1
@usr - Sono completamente d'accordo. Null è tuo amico. IMHO, non esiste un modo più chiaro per rappresentare un valore mancante. Può aiutarti a trovare i bug, può aiutarti a gestire le condizioni di errore, può aiutarti a chiedere perdono (provare / catturare). Cosa non va ?!
Scuba Steve,

4
una delle prime regole di progettazione del software è "Controlla sempre null". Perché anche questo sia un problema mi fa impazzire.
MattE

1
@MattE - Assolutamente. Perché dovrei progettare un modello di progettazione quando davvero, Null è una buona cosa da avere nella tua cassetta degli attrezzi di base. Quello che Graham stava suggerendo, IMHO, mi sembra troppo ingegneristico.
Scuba Steve,

Risposte:


20

Molte cose sono meglio da restituire che null.

  • Una stringa vuota ("")
  • Una collezione vuota
  • Una monade "opzionale" o "forse"
  • Una funzione che silenziosamente non fa nulla
  • Un oggetto pieno di metodi che silenziosamente non fanno nulla
  • Un valore significativo che rifiuta la premessa errata della domanda
    (che è ciò che ti ha fatto considerare nullo in primo luogo)

Il trucco è capire quando farlo. Null è davvero bravo a farti esplodere in faccia ma solo quando lo tocchi senza controllarlo. Non è bravo a spiegare perché.

Quando non è necessario esplodere e non si desidera verificare la presenza di null, utilizzare una di queste alternative. Può sembrare strano restituire una raccolta se contiene solo 0 o 1 elementi, ma è davvero bravo a lasciarti gestire silenziosamente i valori mancanti.

Le monadi sembrano fantasiose, ma qui tutto ciò che stanno facendo è farti capire che le dimensioni valide per la raccolta sono 0 e 1. Se le hai, considerale.

Ognuno di questi fa un lavoro migliore nel chiarire le tue intenzioni rispetto a null. Questa è la differenza più importante.

Può aiutare a capire perché stai tornando affatto piuttosto che semplicemente lanciare un'eccezione. Le eccezioni sono essenzialmente un modo per rifiutare un'ipotesi (non sono l'unico modo). Quando chiedi il punto in cui due linee si intersecano, stai assumendo che si intersecino. Potrebbero essere paralleli. Dovresti lanciare un'eccezione "linee parallele"? Bene, ma ora devi gestire quell'eccezione altrove o lasciarlo arrestare il sistema.

Se preferisci rimanere dove sei, puoi trasformare il rifiuto di quell'assunto in una sorta di valore in grado di esprimere risultati o rifiuti. Non è così strano. Lo facciamo sempre in algebra . Il trucco è rendere significativo quel valore in modo che possiamo capire cosa è successo.

Se il valore restituito può esprimere risultati e rifiuti, deve essere inviato a un codice in grado di gestirli entrambi. Il polimorfismo è davvero potente da usare qui. Invece di scambiare assegni null per isPresent()assegni è possibile scrivere codice che si comporta bene in entrambi i casi. Sostituendo valori vuoti con valori predefiniti o facendo in silenzio nulla.

Il problema con null è che può significare troppe cose. Quindi finisce per distruggere le informazioni.


Nel mio esperimento di pensiero nullo preferito ti chiedo di immaginare una complessa macchina Rube goldbergiana che segnali messaggi codificati usando la luce colorata raccogliendo lampadine colorate da un nastro trasportatore, avvitandole in una presa e accendendole. Il segnale è controllato dai diversi colori delle lampadine posizionate sul nastro trasportatore.

Ti è stato chiesto di rendere questa cosa orribilmente costosa conforme a RFC3.14159 che afferma che dovrebbe esserci un indicatore tra i messaggi. Il marcatore non dovrebbe essere affatto luce. Dovrebbe durare esattamente per un impulso. Lo stesso periodo di tempo in cui una singola luce colorata brilla normalmente.

Non puoi semplicemente lasciare spazi tra i messaggi perché l'attacco fa scattare allarmi e si ferma se non riesce a trovare una lampadina da inserire nella presa.

Chiunque abbia familiarità con questo progetto rabbrividisce al pensiero di toccare il macchinario o il suo codice di controllo. Non è facile da cambiare. cosa fai? Bene, puoi immergerti e iniziare a rompere le cose. Forse aggiorna questo design orribile. Sì, potresti renderlo molto meglio. Oppure potresti parlare con il bidello e iniziare a raccogliere le lampadine bruciate.

Questa è la soluzione polimorfica. Il sistema non ha nemmeno bisogno di sapere nulla è cambiato.


È davvero bello se riesci a farlo. Codificando un rifiuto significativo di un'ipotesi in un valore non è necessario forzare la modifica della domanda.

Quante mele mi devi se ti do 1 mela? -1. Perché ti dovevo 2 mele di ieri. Qui l'ipotesi della domanda "mi devi" è respinta con un numero negativo. Anche se la domanda era sbagliata, in questa risposta sono codificate informazioni significative. Respingendolo in modo significativo la domanda non è costretta a cambiare.

Tuttavia, a volte cambiare la domanda in realtà è la strada da percorrere. Se il presupposto può essere reso più affidabile, sebbene utile, considera di farlo.

Il tuo problema è che, normalmente, gli aggiornamenti provengono dai giocatori. Ma hai scoperto la necessità di un aggiornamento che non proviene dal giocatore 1 o dal giocatore 2. È necessario un aggiornamento che indichi che il tempo è scaduto. Nessuno dei due giocatori lo dice, quindi cosa dovresti fare? Lasciare il giocatore nullo sembra così allettante. Ma distrugge le informazioni. Stai cercando di codificare la conoscenza in un buco nero.

Il campo del giocatore non ti dice chi si è appena spostato. Pensi che lo faccia ma questo problema dimostra che è una bugia. Il campo del giocatore ti dice da dove proviene l'aggiornamento. L'aggiornamento scaduto viene dall'orologio di gioco. La mia raccomandazione è di dare all'orologio il merito che merita e cambiare il nome del playercampo in qualcosa del genere source.

In questo modo se il server si sta spegnendo e deve sospendere il gioco, può inviare un aggiornamento che segnala ciò che sta accadendo e si elenca come fonte dell'aggiornamento.

Questo significa che non dovresti mai lasciare qualcosa fuori? No. Ma devi considerare quanto bene non si può presumere che significhi davvero un singolo valore predefinito ben noto. Niente è davvero facile da usare. Quindi fai attenzione quando lo usi. C'è una scuola di pensiero che abbraccia non favorire nulla . Si chiama convenzione sulla configurazione .

Mi piace da solo, ma solo quando la convenzione è chiaramente comunicata in qualche modo. Non dovrebbe essere un modo per confondere i neofiti. Ma anche se lo stai usando, null non è ancora il modo migliore per farlo. Null è un nulla pericoloso . Null finge di essere qualcosa che puoi usare fino a quando non lo usi, quindi fa un buco nell'universo (rompe il tuo modello semantico). A meno che non si crei un buco nell'universo è il comportamento di cui hai bisogno perché stai scherzando con questo? Usa qualcos'altro.


9
"restituisce una raccolta se ha sempre solo 0 o 1 elementi" - Mi dispiace ma non credo di averla :( Dal mio POV facendo questo (a) aggiunge stati non validi non necessari alla classe (b) richiede di documentare che la raccolta deve contenere al massimo 1 elemento (c) non sembra risolvere il problema, dal momento che chiedere comunque se la raccolta contenga il suo unico elemento prima di accedervi è necessario, altrimenti verrà generata un'eccezione?
gaazkam

2
@gaazkam vedi? Te l'ho detto che infastidisce le persone. Eppure è esattamente quello che hai fatto ogni volta che usi la stringa vuota.
candied_orange

16
Cosa succede quando esiste un valore valido che risulta essere una stringa o un set vuoto?
Blrfl,

5
Ad ogni modo @gaazkam, non controlli esplicitamente se la collezione contiene elementi. Il ciclo (o il mapping) su un elenco vuoto è un'azione sicura che non ha alcun effetto.
RubberDuck,

3
So che stai rispondendo alla domanda su cosa dovrebbe fare qualcuno invece di restituire null, ma in realtà non sono d'accordo con molti di questi punti. Tuttavia, poiché non siamo specifici della lingua e c'è una ragione per infrangere ogni regola che mi rifiuto di sottovalutare
Liath

15

nullnon è davvero il problema qui. Il problema è come la maggior parte degli attuali linguaggi di programmazione gestisce le informazioni mancanti; non prendono sul serio questo problema (o almeno non forniscono il supporto e la sicurezza necessari alla maggior parte dei terrestri per evitare le insidie). Questo porta a tutti i problemi che abbiamo imparato a temere (Javas NullPointerException, Segfaults, ...).

Vediamo come affrontare il problema in modo migliore.

Altri hanno suggerito il modello degli oggetti nulli . Mentre penso che sia applicabile a volte, ci sono molti scenari in cui non esiste un valore predefinito unico per tutte le dimensioni. In questi casi, i consumatori delle informazioni eventualmente assenti devono essere in grado di decidere autonomamente cosa fare se tali informazioni mancano.

Esistono linguaggi di programmazione che forniscono sicurezza contro puntatori null (la cosiddetta sicurezza nulla) al momento della compilazione. Per fare alcuni esempi moderni: Kotlin, Rust, Swift. In quelle lingue, il problema delle informazioni mancanti è stato preso sul serio e l'uso di null è totalmente indolore: il compilatore ti impedirà di dereferenziare i punti null.
In Java, sono stati fatti sforzi per stabilire le annotazioni @ Nullable / @ NotNull insieme ai plugin compilatore + IDE per ottenere lo stesso effetto. Non ha avuto molto successo, ma è fuori dalla portata di questa risposta.

Un tipo esplicito e generico che denota una possibile assenza è un altro modo per affrontarlo. Ad esempio in Java c'è java.util.Optional. Può funzionare:
a livello di progetto o di azienda, è possibile stabilire regole / linee guida

  • utilizzare solo librerie di terze parti di alta qualità
  • usa semprejava.util.Optional invece dinull

La tua comunità linguistica / ecosistema potrebbe aver escogitato altri modi per risolvere questo problema meglio del compilatore / interprete.


Potresti gentilmente elaborare, in che modo Java Optionals è meglio dei null? Mentre leggo la documentazione , il tentativo di ottenere un valore dall'opzionale produce un'eccezione se il valore è assente; quindi sembrerebbe che siamo nello stesso posto: è necessario un controllo e se ne dimentichiamo uno, siamo fregati?
gaazkam,

3
@gaazkam Hai ragione, non fornisce sicurezza in fase di compilazione. Inoltre, l'Opzionale stesso potrebbe essere nullo, un altro problema. Ma è esplicito (rispetto alle variabili che possono essere solo commenti null / doc). Ciò offre un vero vantaggio se, per l'intera base di codice, viene impostata una regola di codifica. Non è possibile impostare nulla su null. Se le informazioni potrebbero essere assenti, utilizzare Opzionale. Ciò forza l'esplicitazione. I soliti controlli null diventano quindi chiamate aOptional.isPresent
marstato

8
@marstato sostituendo i controlli null con isPresent() non è l'uso raccomandato di Opzionale
candied_orange

10

Non vedo alcun merito nell'affermazione che null è un male. Ho controllato le discussioni a riguardo e mi rendono solo impaziente e irritato.

Null può essere usato male, come "valore magico", quando in realtà significa qualcosa. Ma dovresti fare una programmazione piuttosto distorta per arrivarci.

Null dovrebbe significare "non c'è", che non viene creato (ancora) o (già) sparito o non fornito se si tratta di un argomento. Non è uno stato, manca tutto. In un'ambientazione OO in cui le cose vengono create e distrutte per tutto il tempo che è una condizione valida e significativa diversa da qualsiasi stato.

Con null vieni schiacciato in fase di esecuzione in cui vieni schiacciato in fase di compilazione con una sorta di alternativa nulla sicura per i tipi.

Non del tutto vero, posso ricevere un avviso per non verificare la presenza di null prima di utilizzare la variabile. E non è lo stesso. Potrei non voler risparmiare le risorse di un oggetto che non ha scopo. E, soprattutto, dovrò verificare l'alternativa allo stesso modo per ottenere il comportamento desiderato. Se dimentico di farlo, preferirei ottenere una NullReferenceException in fase di esecuzione piuttosto che un comportamento indefinito / non intenzionale.

C'è un problema da tenere presente. In un contesto multi-thread, è necessario proteggersi dalle condizioni di competizione assegnando a una variabile locale prima di eseguire il controllo per null.

Il problema con null è che può significare lontano per molte cose. Quindi finisce per distruggere le informazioni.

No, non significa / non dovrebbe significare nulla, quindi non c'è nulla da distruggere. Il nome e il tipo della variabile / argomento diranno sempre cosa manca, questo è tutto ciò che c'è da sapere.

Modificare:

Sono a metà del video candied_orange collegato in una delle sue altre risposte sulla programmazione funzionale ed è molto interessante. Posso vederne l'utilità in determinati scenari. L'approccio funzionale che ho visto finora, con pezzi di codice che volano in giro, in genere mi fa rabbrividire quando utilizzato in un ambiente OO. Ma credo che abbia il suo posto. Da qualche parte. E immagino che ci saranno lingue che fanno un buon lavoro nascondendo quello che percepisco come un casino confuso ogni volta che lo incontro in C #, linguaggi che forniscono invece un modello pulito e praticabile.

Se quel modello funzionale è la tua mentalità / quadro, non c'è davvero alcuna alternativa a spingere in avanti eventuali disavanzi come descrizioni di errori documentate e alla fine lasciare che il chiamante gestisca la spazzatura accumulata che è stata trascinata fino al punto di inizio. Apparentemente è così che funziona la programmazione funzionale. Chiamalo un limite, chiamalo un nuovo paradigma. Potresti considerarlo un hack per i discepoli funzionali che consente loro di continuare un po 'oltre il sentiero empio, potresti essere felice di questo nuovo modo di programmare che scatena nuove entusiasmanti possibilità. Qualunque cosa, mi sembra inutile fare un passo in quel mondo, rimandare al tradizionale modo OO di fare le cose (sì, possiamo fare eccezioni, scusate), scegliere qualche concetto da esso,

Qualsiasi concetto può essere usato nel modo sbagliato. Da dove mi trovo, le "soluzioni" presentate non sono alternative per un uso appropriato di null. Sono concetti di un altro mondo.


9
Nulla distrugge la conoscenza di che tipo di nulla rappresenta. C'è il tipo di niente che dovrebbe essere tranquillamente ignorato. Il tipo di nulla che deve arrestare il sistema per evitare uno stato non valido. Il tipo di niente che significa che il programmatore ha sbagliato e deve riparare il codice. Il tipo di niente che significa che l'utente ha chiesto qualcosa che non esiste e deve essere educatamente detto. Scusa no. Tutti questi sono stati codificati come null. Per questo motivo, se codifichi uno di questi come null, non dovresti essere sorpreso se nessuno sa cosa intendi. Ci stai forzando a indovinare dal contesto.
candied_orange

3
@candied_orange Puoi fare esattamente lo stesso argomento su qualsiasi tipo facoltativo: Nonerappresenta ...
Deduplicatore

3
@candied_orange Non capisco ancora cosa intendi. Diversi tipi di niente? Se usi null quando avresti dovuto usare un enum forse? C'è solo un tipo di niente. La ragione per cui qualcosa non può essere nulla può variare ma ciò non cambia il nulla né la risposta appropriata a qualcosa che non è nulla. Se ti aspetti un valore in un negozio che non c'è e che è degno di nota, lanci o accedi al momento in cui esegui una query e non ottieni nulla, non passi null come argomento a un metodo e quindi in quel metodo prova a capire perché hai ottenuto null. Null non sarebbe l'errore.
Martin Maat,

2
Null è l'errore perché hai avuto la possibilità di codificare il significato del nulla. La tua comprensione del nulla non richiede una gestione immediata del nulla. 0, null, NaN, stringa vuota, set vuoto, vuoto, oggetto null, non applicabile, eccezione non implementata, nulla viene fornito in molte molte molte forme. Non devi dimenticare quello che sai ora solo perché non vuoi occuparti di ciò che conosci ora. Se ti chiedo di prendere la radice quadrata di -1 puoi o lanciare un'eccezione per farmi sapere che è impossibile e dimenticare tutto o inventare numeri immaginari e preservare ciò che sai.
candied_orange

1
@MartinMaat sembra che una funzione debba generare un'eccezione ogni volta che le tue aspettative vengono violate. Questo semplicemente non è vero. Ci sono spesso casi angolari da affrontare. Se proprio non riesci a vederlo, leggi questo
candied_orange

7

La tua domanda.

Ma, ma - per mia ingenuità, fammi urlare - a volte un valore può essere significativamente assente!

Completamente a bordo con te lì. Tuttavia, ci sono alcuni avvertimenti che tratterò tra un secondo. Ma prima:

I nullità sono malvagi e dovrebbero essere evitati quando possibile.

Penso che questa sia un'affermazione ben intenzionata ma troppo generica.

I nullità non sono cattivi. Non sono un antipasto. Non sono qualcosa che deve essere evitato a tutti i costi. Tuttavia, i null sono soggetti a errori (dal mancato controllo di null).

Ma non capisco come non riuscire a controllare null sia in qualche modo la prova che null è intrinsecamente sbagliato da usare.

Se null è male, lo sono anche gli indici di array e i puntatori C ++. Loro non sono. Sono facili da sparare al piede con, ma non dovrebbero essere evitati a tutti i costi.

Per il resto di questa risposta, ho intenzione di adattare l'intenzione di "i null sono cattivi" a un più sfumato "i null dovrebbero essere gestiti in modo responsabile ".


La tua soluzione.

Mentre sono d'accordo con la tua opinione sull'uso di null come regola globale; Non sono d'accordo con il tuo uso di null in questo caso particolare.

Riassumendo il feedback che segue: stai fraintendendo lo scopo dell'ereditarietà e del polimorfismo. Mentre il tuo argomento per usare null ha validità, la tua ulteriore "prova" del perché l'altro modo è cattivo è costruita sull'abuso dell'eredità, rendendo i tuoi punti in qualche modo irrilevanti per il problema null.

Alla maggior parte degli aggiornamenti è naturalmente associato un giocatore: dopo tutto, è necessario sapere quale giocatore ha effettuato quale mossa in questo turno. Tuttavia, alcuni aggiornamenti non possono avere un giocatore associato in modo significativo con loro.

Se questi aggiornamenti sono significativamente diversi (quali sono), sono necessari due tipi di aggiornamento separati.

Considera i messaggi, per esempio. Hai messaggi di errore e messaggi chat IM. Ma sebbene possano contenere dati molto simili (un messaggio stringa e un mittente), non si comportano allo stesso modo. Dovrebbero essere usati separatamente, come classi diverse.

Quello che stai facendo ora è effettivamente la differenziazione tra due tipi di aggiornamento in base alla nullità della proprietà Player. Ciò equivale a decidere tra un messaggio di messaggistica istantanea e un messaggio di errore basato sull'esistenza di un codice di errore. Funziona, ma non è l'approccio migliore.

  • Il codice JS ora riceverà semplicemente un oggetto senza Player come proprietà definita, che è lo stesso di se fosse null poiché entrambi i casi richiedono di verificare se il valore è presente o assente;

La tua tesi si basa su errori di polimorfismo. Se si dispone di un metodo che restituisce un GameUpdateoggetto, in genere non dovrebbe mai interessarsi di distinguere tra GameUpdate, PlayerGameUpdateo NewlyDevelopedGameUpdate.

Una classe base deve avere un contratto pienamente funzionante, ovvero le sue proprietà sono definite sulla classe base e non sulla classe derivata (detto ciò, il valore di queste proprietà base può ovviamente essere ignorato nelle classi derivate).

Questo rende discutibile il tuo argomento. In buona pratica, non dovresti mai preoccuparti che il tuo GameUpdateoggetto abbia o meno un giocatore. Se stai provando ad accedere alla Playerproprietà di a GameUpdate, stai facendo qualcosa di illogico.

I linguaggi digitati come C # non ti permetterebbero nemmeno di provare ad accedere alla Playerproprietà di a GameUpdateperché GameUpdatesemplicemente non ha la proprietà. Javascript è considerevolmente più lento nel suo approccio (e non richiede precompilazione), quindi non si preoccupa di correggerti e lo fa esplodere in fase di esecuzione.

  • Se un altro campo nullable fosse aggiunto alla classe GameUpdate, dovrei essere in grado di utilizzare l'ereditarietà multipla per continuare con questo desing - ma MI è un male a sé stante (secondo i programmatori più esperti) e, cosa più importante, C # non lo fa ce l'ho quindi non posso usarlo.

Non dovresti creare classi in base all'aggiunta di proprietà nullable. Dovresti creare classi basate su tipi di aggiornamenti di gioco funzionalmente unici .


Come evitare i problemi con null in generale?

Penso che sia rilevante approfondire come è possibile esprimere in modo significativo l'assenza di un valore. Questa è una raccolta di soluzioni (o approcci sbagliati) in nessun ordine particolare.

1. Usa null comunque.

Questo è l'approccio più semplice. Tuttavia, ti stai iscrivendo per un sacco di controlli nulli e (quando non lo fai) risolvendo le eccezioni di riferimento null.

2. Utilizzare un valore senza significato non nullo

Un semplice esempio qui è indexOf():

"abcde".indexOf("b"); // 1
"abcde".indexOf("f"); // -1

Invece di null, ottieni -1. A livello tecnico, questo è un valore intero valido. Tuttavia, un valore negativo è una risposta senza senso poiché si prevede che gli indici siano numeri interi positivi.

Logicamente, questo può essere usato solo nei casi in cui il tipo restituito consente più valori possibili di quanti possano essere restituiti in modo significativo (indexOf = numeri interi positivi; int = numeri interi negativi e positivi)

Per i tipi di riferimento, è ancora possibile applicare questo principio. Ad esempio, se si dispone di un'entità con un ID intero, è possibile restituire un oggetto fittizio con l'ID impostato su -1, anziché su null.
Devi semplicemente definire uno "stato fittizio" in base al quale puoi misurare se un oggetto è effettivamente utilizzabile o meno.

Si noti che non si dovrebbe fare affidamento su valori di stringa predeterminati se non si controlla il valore di stringa. Potresti considerare di impostare il nome di un utente su "null" quando desideri restituire un oggetto vuoto, ma potresti riscontrare problemi quando Christopher Null si iscrive al tuo servizio .

3. Lancia invece un'eccezione.

No semplicemente no. Le eccezioni non devono essere gestite per il flusso logico.

Detto questo, ci sono alcuni casi in cui non vuoi nascondere l'eccezione (nullreference), ma piuttosto mostrare un'eccezione appropriata. Per esempio:

var existingPerson = personRepository.GetById(123);

Questo può lanciare un PersonNotFoundException(o simile) quando non c'è persona con ID 123.

Un po 'ovviamente, questo è accettabile solo per i casi in cui il mancato reperimento di un articolo rende impossibile eseguire ulteriori elaborazioni. Se non è possibile trovare un elemento e l'applicazione può continuare, non si dovrebbe MAI generare un'eccezione . Non posso sottolineare abbastanza.


5

Il motivo per cui i null sono cattivi non è che il valore mancante è codificato come null, ma che qualsiasi valore di riferimento può essere nullo. Se hai C #, probabilmente ti perderai comunque. Non vedo un punto ro usare alcuni opzionali lì perché un tipo di riferimento è già facoltativo. Ma per Java ci sono le annotazioni @Notnull, che sembra essere possibile impostare a livello di pacchetto , quindi per i valori che si possono perdere è possibile utilizzare l'annotazione @Nullable.


Sì! Il problema è un valore predefinito privo di senso.
Deduplicatore

Non sono d'accordo. La programmazione in uno stile che evita null porta a un minor numero di variabili di riferimento null, portando a un minor numero di variabili di riferimento che non dovrebbero essere null essendo null. Non è al 100% ma forse è al 90%, il che vale la pena IMO.
Ripristina Monica il

1

I nulli non sono cattivi, non c'è realisticamente alcun modo per attuare nessuna delle alternative senza a un certo punto affrontare qualcosa che sembra un nulla.

I null sono comunque pericolosi . Proprio come qualsiasi altro costrutto di basso livello se sbagli, non c'è niente sotto per catturarti.

Penso che ci siano molti argomenti validi contro null:

  • Che se sei già in un dominio di alto livello, ci sono modelli più sicuri rispetto all'uso del modello di basso livello.

  • A meno che non abbiate bisogno dei vantaggi di un sistema di basso livello e non siate preparati ad essere cauti, allora forse non dovreste usare un linguaggio di basso livello.

  • ecc ...

Questi sono tutti validi e inoltre non dover fare lo sforzo di scrivere getter, setter ecc. È visto come un motivo comune per non aver seguito quel consiglio. Non è davvero sorprendente che molti programmatori siano giunti alla conclusione che i null sono pericolosi e pigri (una cattiva combinazione!), Ma come tanti altri consigli "universali" non sono universali come sono formulati.

Le brave persone che hanno scritto la lingua pensavano che sarebbero state utili per qualcuno. Se capisci le obiezioni e pensi ancora che siano giuste per te, nessun altro saprà di meglio.


Il fatto è che il "consiglio universale" di solito non è definito "universalmente" come sostengono le persone. Se trovi la fonte originale di tali consigli, di solito parlano delle avvertenze che i programmatori esperti conoscono. Quello che succede è che, quando qualcun altro legge il consiglio, tendono ad ignorare quelle avvertenze e ricordano solo la parte del "consiglio universale", quindi quando mettono in relazione quel consiglio con gli altri, viene fuori molto più "universale" di quello che l'originatore intendeva .
Nicol Bolas

1
@NicolBolas, hai ragione, ma la fonte originale non è il consiglio che viene citato la maggior parte delle persone, è il messaggio trasmesso. Il punto che desideravo sottolineare era semplicemente che a molti programmatori è stato detto "non fare mai X, X è cattivo / cattivo / qualunque cosa" e, esattamente come dici tu, mancano molti avvertimenti. Forse sono stati lasciati cadere, forse non sono mai stati presenti, il risultato finale è lo stesso.
drjpizzle

0

Java ha una Optionalclasse con un esplicito ifPresent+ lambda per accedere a qualsiasi valore in modo sicuro. Questo può essere fatto anche in JS:

Vedi questa domanda StackOverflow .

A proposito: molti puristi troveranno questo uso discutibile, ma io non la penso così. Esistono funzionalità opzionali.


Non può essere un riferimento anche a un java.lang.Optionalessere null?
Deduplicatore,

@Deduplicator No, Optional.of(null)genererebbe un'eccezione, Optional.ofNullable(null)darebbe Optional.empty()il valore non-lì.
Joop Eggen,

E Optional<Type> a = null?
Deduplicatore,

@Deduplicator vero, ma lì dovresti usare Optional<Optional<Type>> a = Optional.empty();;) - In altre parole in JS (e in altre lingue) tali cose non saranno fattibili. Scala con valori predefiniti farebbe. Java forse con un enum. JS Dubito: Optional<Type> a = Optional.empty();.
Joop Eggen,

Quindi, siamo passati da una direzione indiretta e potenziale nullo vuota, a due più alcuni metodi extra sull'oggetto interposto. È un bel risultato.
Deduplicatore,

0

CAR Hoare ha definito nulli il suo "errore di miliardi di dollari". Tuttavia, è importante notare che pensava che la soluzione non fosse "nessun null ovunque" ma invece "puntatori non annullabili come predefiniti e null solo dove sono richiesti semanticamente".

Il problema con null nelle lingue che ripetono il suo errore è che non si può dire che una funzione si aspetti dati non annullabili; è fondamentalmente inesprimibile nella lingua. Ciò costringe le funzioni a includere un sacco di inutili piastre di caldaia a gestione null estranee inutili e dimenticare un controllo null è una fonte comune di bug.

Per aggirare questa fondamentale mancanza di espressività, alcune comunità (ad esempio la comunità Scala) non usano null. In Scala, se si desidera un valore nullable di tipo T, si prende un argomento di tipo Option[T]e se si desidera un valore non nullable, basta prendere un T. Se qualcuno passa un null nel tuo codice, il tuo codice esploderà. Tuttavia, si ritiene che il codice chiamante sia il codice buggy. Optionè un bel sostituto di null, tuttavia, poiché i modelli comuni di gestione dei null sono estratti in funzioni di libreria come .map(f)o .getOrElse(someDefault).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.