I numeri nullable sono preferibili ai numeri magici?

22

Ultimamente ho avuto un po 'di dibattito con un collega. In particolare stiamo utilizzando C #, ma questo potrebbe essere applicato a qualsiasi lingua con tipi nullable. Supponiamo ad esempio di avere un valore che rappresenta un massimo. Tuttavia, questo valore massimo è facoltativo. Io sostengo che sarebbe preferibile un numero nullable. Il mio collaboratore preferisce usare zero, citando un precedente. Certo, cose come i socket di rete hanno spesso usato zero per rappresentare un timeout illimitato. Se dovessi scrivere il codice che si occupa di socket oggi, utilizzerei personalmente un valore nullable, poiché ritengo che sarebbe meglio rappresentare il fatto che NON ci sia un timeout.

Quale rappresentazione è migliore? Entrambi richiedono una condizione che verifichi il valore che significa "nessuno", ma credo che un tipo nullable trasmetta l'intento un po 'meglio.

    
posta Matt H 30.03.2012 - 18:08
fonte

10 risposte

23

Si consideri:

  • Lingua,

  • quadro,

  • Contesto.

1. Lingua

Usare ∞ può essere una soluzione per un massimo.

  • JavaScript, ad esempio, ha un infinito. C # non1.

  • Ada, ad esempio, ha intervalli. C # no.

In C #, c'è int.MaxValue , ma non puoi usarlo nel tuo caso. int.MaxValue è il numero intero massimo, 2.147.483.647. Se nel tuo codice, hai un valore massimo di qualcosa, come una pressione massima accettata prima che qualcosa esploda, utilizzando 2.147.483.647 non ha senso.

2. Quadro

.NET Framework è piuttosto incoerente su questo punto e il suo uso di valori magici può essere criticato.

Ad esempio, "Hello".IndexOf("Z") restituisce un valore magico -1 . forse rende più facile (o?) Manipolare il risultato:

int position = "Hello".IndexOf("Z");
if (position > 0)
{
    DoSomething(position);
}

anziché utilizzare una struttura personalizzata:

SearchOccurrence occurrence = "Hello".IndexOf("Z");
if (occurrence.IsFound)
{
    DoSomething(occurrence.StartOffset);
}

ma non è affatto intuitivo. Perché -1 e non -123 ? Un principiante potrebbe anche pensare erroneamente che 0 significhi "Non trovato" o semplicemente errato (position >= 0) .

3. Contesto

Se il tuo codice è correlato ai timeout nei socket di rete, l'utilizzo di qualcosa che è stato utilizzato da tutti per decenni per essere coerente non è una cattiva idea . Soprattutto, 0 per un timeout è molto chiaro: è un valore che non può essere zero. L'uso di una classe personalizzata in questo caso può rendere le cose più difficili da comprendere:

class Timeout
{
    // A value indicating whether there is a timeout.
    public bool IsTimeoutEnabled { get; set; }

    // The duration of the timeout, in milliseconds.
    public int Duration { get; set; }
}
  • Posso impostare Duration su 0 se IsTimeoutEnabled è vero?
  • Se IsTimeoutEnabled è false, cosa succede se imposto Duration su 100?

Questo può portare a più errori. Immagina il seguente pezzo di codice:

this.currentOperation.Timeout = new Timeout
{
    // Set the timeout to 200 ms.; we don't want this operation to be longer than that.
    Duration = 200,
};

this.currentOperation.Run();

L'operazione viene eseguita per dieci secondi. Riesci a vedere cosa c'è di sbagliato in questo codice, senza leggere la documentazione di Timeout class?

Conclusione

  • null esprime bene l'idea che il valore non sia qui. Non è fornito. Non disponibile. Non è né un numero, né una stringa zero / vuota o qualsiasi altra cosa. Non usarlo per i valori massimi o minimi.

  • int.MaxValue è strongmente correlato alla lingua stessa. Non utilizzare int.MaxValue per un limite di velocità massimo della classe Vehicle o una velocità massima accettabile per un aeromobile, ecc.

  • Evita valori magici come -1 nel codice. Sono fuorvianti e portano a errori nel codice.

  • Crea la tua classe che sarebbe più semplice, con i valori minimi / massimi specificati. Ad esempio VehicleSpeed può avere VehicleSpeed.MaxValue .

  • Non seguire alcuna linea guida precedente e utilizzare i valori magici se è una convenzione generale per decenni in un campo molto specifico, utilizzato dalla maggior parte delle persone che scrivono codice in questo campo.

  • Non dimenticare di mischiare approcci. Ad esempio:

    class DnsQuery
    {
        public const int NoTimeout = 0;
    
        public int Timeout { get; set; }
    }
    
    this.query.Timeout = 0; // For people who are familiar with timeouts set to zero.
    // or
    this.query.Timeout = DnsQuery.NoTimeout; // For other people.
    

¹ Puoi creare il tuo tipo che include l'infinito. Qui, sto parlando solo del tipo int nativo.

    
risposta data 30.03.2012 - 18:44
fonte
12

Null non è migliore di un numero magico.

L'importante è NOME i valori che hanno effetti magici, se devi avere tali valori, e assicurarti che le definizioni di quei nomi siano un posto che sarà visto da chiunque si imbatta nel valore magico e nel wtf .

if (timeout == 4298435) ... // bad.
if (timeout == null) ... // bad.
if (timeout == NEVER_TIME_OUT) ... // yay! puppies and unicorns!
    
risposta data 30.03.2012 - 18:45
fonte
10

Il codice MAGIC_NUMBER dovrebbe assolutamente essere sempre evitato laddove possibile. null è un'espressione di intenti molto più chiara.

    
risposta data 30.03.2012 - 18:19
fonte
6

In C #, molte classi CLR hanno un membro Empty statico:

  • System.String.Empty
  • System.EventArgs.Empty
  • System.Guid.Empty
  • System.Drawing.Rectangle.Empty
  • System.Windows.Size.Empty

Questo ti impedisce di dover ricordare se usare un valore magico o usare null per costruire un oggetto vuoto.

Ma cosa succede se hai a che fare con un tipo di valore semplice come int ? In tal caso, considera se stai cadendo vittima di Primitive Obsession . È possibile che la tua proprietà numerica apparentemente semplice trarrebbe vantaggio dalla sua classe o struttura, che ti consentirebbe di specificare il membro Empty e anche di aggiungere altro comportamento specifico a quel tipo di valore.

    
risposta data 30.03.2012 - 19:46
fonte
3

In questo caso, il valore null è un ottimo modo per indicare che non c'è un massimo. Generalmente quando il caso speciale significa che il valore in questione non si applica, che semplicemente non vuoi la funzione che configura, null è una buona indicazione di ciò.

Un problema con l'utilizzo di null per rappresentare casi speciali è che esiste un solo valore nullo e potrebbero esserci più casi speciali. In questo caso, vorrei passare un'enumerazione come parametro aggiuntivo, che può indicare un caso speciale o utilizzare normalmente il valore int. (Questo è essenzialmente ciò che Nullable < > fa per te, sebbene usi un valore booleano invece di un enum e combini i parametri in un'unica struttura.)

    
risposta data 30.03.2012 - 19:12
fonte
3

In questo caso, penso che un tipo nullable abbia perfettamente senso.

Null significa assenza di valore. Questo è un concetto nettamente diverso da un numero con valore 0.

Se vuoi dire "Se non ti do un valore, usa il massimo" allora passare a null è il modo esatto corretto per esprimerlo.

    
risposta data 30.03.2012 - 19:27
fonte
0

Null è peggio da usare di MagicNumber . Null rappresenta l'idea espressa meglio, ma non è coerente su tutte le piattaforme nel modo in cui si comporta, utilizzando MagicNumber funziona sempre allo stesso modo che è vantaggioso.

a seconda dell'ambiente / lingua utilizzata null potrebbe

  • semplicemente essere 0
  • potrebbe non essere un valore legale
  • può produrre risultati imprevisti a causa della logica a tre vie

MagicNumber si comporta sempre allo stesso modo.

    
risposta data 30.03.2012 - 20:06
fonte
0

Se ti dimentichi di controllare il numero magico (succederà a destra), allora un numero magico continuerà per un po 'con dati privi di senso. Molto meglio avere un null che provoca un'eccezione il più presto possibile.

    
risposta data 02.04.2012 - 23:49
fonte
0

Null: valore di errore comune, non specificato, vuoto o assenza di valore.

Zero: un valore reale, ma non necessariamente logico o intuitivo (in questo contesto). Anche un valore comune in fase di inizializzazione.

Nel contesto del tuo problema, la proprietà timeoutInMilliseconds è facoltativa e non si fa menzione del fatto che il sovraccarico di questo approccio lo squalificherebbe come opzione.

Conclusione: ci sono delle eccezioni e le soluzioni variano a seconda delle lingue e del dominio; in questo caso, sceglierei Null. Dove (credo) alcune persone sbagliano è quando non separano bene i dati dall'interfaccia. Si aspettano solo che ogni cliente legga la documentazione (o implementazione) per determinare come questi valori speciali debbano essere usati / gestiti - i casi speciali si infiltrano nel programma del cliente e possono essere abbastanza poco chiari. Aggiungendo un buon livello di astrazione, l'utilizzo può essere molto più chiaro.

    
risposta data 03.04.2012 - 02:36
fonte
-1

Null non è l'unica alternativa a un numero magico.

public static int NO_TIMEOUT = 0;  // javaish

Null è malvagio. Nell'esempio sopra, potresti essere in grado di farla franca poiché il codice sarebbe ovviamente in grado di gestire un null. Ma in generale ciò che accade quando si inizia a passare null in giro è che prima o poi si ottiene un'eccezione di puntatore nullo. Potrebbe non succedere quando scrivi per la prima volta il codice, ma il codice viene mantenuto per molto più tempo rispetto alla prima versione. Spesso viene gestito da persone che non conoscono tanto il sistema quanto gli sviluppatori originali.

Scala (per esempio) ha una bella alternativa nella classe Option. La classe Option ha uno dei due valori, Some - che racchiude il valore desiderato e None - che non ha valore.

Questo rende ovvio a qualsiasi sviluppatore che potrebbe non esserci un valore e che tu avessi un codice migliore per quello. Beh, dovrebbe comunque essere ovvio.

E non tutti i numeri magici sono un problema. A seconda del contesto 0, 1, 1024 ecc. Tutto può essere ovvio. 347? Sì, quello che dovresti evitare. : -)

    
risposta data 30.03.2012 - 18:35
fonte

Leggi altre domande sui tag