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 GameUpdate
oggetto, in genere non dovrebbe mai interessarsi di distinguere tra GameUpdate
, PlayerGameUpdate
o 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 GameUpdate
oggetto abbia o meno un giocatore. Se stai provando ad accedere alla Player
proprietà di a GameUpdate
, stai facendo qualcosa di illogico.
I linguaggi digitati come C # non ti permetterebbero nemmeno di provare ad accedere alla Player
proprietà di a GameUpdate
perché GameUpdate
semplicemente 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.