I tipi nullable sono preferibili ai numeri magici?


22

Ultimamente ho avuto un piccolo dibattito con un collega. Stiamo usando specificamente C #, ma questo potrebbe applicarsi a qualsiasi lingua con tipi nullable. Supponiamo ad esempio di avere un valore che rappresenta un massimo. Tuttavia, questo valore massimo è facoltativo. Sostengo che sarebbe preferibile un numero nullable. Il mio collega preferisce l'uso di zero, citando il precedente. Certo, cose come i socket di rete hanno spesso usato zero per rappresentare un timeout illimitato. Se oggi dovessi scrivere codice relativo ai socket, utilizzerei personalmente un valore nullable, poiché ritengo che rappresenterebbe meglio il fatto che NON esiste alcun timeout.

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


6
Se viene utilizzato un numero, inserirlo in una costante, non direttamente nel codice.
Renato Dinhani,

@ RenatoDinhaniConceição che non può essere una regola generale. Altrimenti si finisce con il softcoding di tutto.
Simon Bergot,

Risposte:


24

Ritenere:

  • Linguaggio,

  • Struttura,

  • Contesto.

1. Lingua

L'uso di ∞ può essere una soluzione per un massimo.

  • JavaScript, ad esempio, ha un infinito. C # non¹.

  • 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, usare 2.147.483.647 non ha senso.

2. Quadro

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

Ad esempio, "Hello".IndexOf("Z")restituisce un valore magico -1. E ' forse rende più facile (lo fa?) Per 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é -1e no -123? Un principiante può anche erroneamente pensare che 0significhi anche "Non trovato" o semplicemente digitare male (position >= 0).

3. Contesto

Se il tuo codice è correlato a timeout nei socket di rete, utilizzare qualcosa che è stato usato da decenni per motivi di coerenza non è una cattiva idea . Soprattutto, 0per 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 capire:

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 Durationsu 0 se IsTimeoutEnabledè vero?
  • Se IsTimeoutEnabledè falso, cosa succede se imposto Durationsu 100?

Questo può portare a più errori. Immagina il seguente 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 dura dieci secondi. Riesci a vedere cosa c'è che non va in questo codice, senza leggere la documentazione della Timeoutclasse?

Conclusione

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

  • int.MaxValueè fortemente correlato alla lingua stessa. Non utilizzare int.MaxValueper un limite di velocità massima della Vehicleclasse o una velocità massima accettabile per un aeromobile, ecc.

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

  • Crea la tua classe che sarebbe più semplice, con i valori minimo / massimo specificati. Ad esempio VehicleSpeedpuò avere VehicleSpeed.MaxValue.

  • Non seguire alcuna linea guida precedente e utilizzare valori magici se si tratta di 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 mescolare gli approcci. Per 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.
    

¹ È possibile creare il proprio tipo che include l'infinito. Qui, sto parlando intsolo di tipo nativo .


1
"usare qualcosa che è stato usato da tutti per decenni per motivi di coerenza non è una cattiva idea" / "Non seguire alcuna linea guida precedente e usare valori magici se si tratta di una convenzione generale per decenni in un campo molto specifico, usato da la maggior parte delle persone scrive codice in questo campo ". - C'è un refuso da qualche parte, penso?
Deworde,

1
@deworde Credo che MainMa si riferisca alle linee guida da lui stesso fornite sopra.
Joshua Drake,

1
Non sono d'accordo sull'indice di esempio, poiché -1 è al di fuori della stringa, che Z sicuramente lo è.
Joshua Drake,

5
"JavaScript, ad esempio, ha un infinito. C # no." - eh?
BlueRaja - Danny Pflughoeft

+1 soprattutto per "Crea la tua classe" che è quello che avrei suggerito. Ogni volta che uno bare intnon esprime abbastanza sul tipo per vincolare il problema, considera una nuova struttura con più informazioni (ad esempio costanti della struttura che rappresentano valori magici, per esempio, o un enum su di essa per indicare). Oppure prendi in considerazione la programmazione del contratto o altre soluzioni, ma penso che una struttura personalizzata sia la più semplice.
CodexArcanum,

12

Null non è migliore di un numero magico.

L'importante è NOME dei valori che hanno effetti magici, se si devono avere tali valori, e assicurarsi che le definizioni di quei nomi siano in qualche posto che possano essere viste da chiunque si imbatta nel valore magico e in wtf.

if (timeout == 4298435) ... // bad.
if (timeout == null) ... // bad.
if (timeout == NEVER_TIME_OUT) ... // yay! puppies and unicorns!

2
Ok, forse dipende più dalla lingua, ma in C # probabilmente lo faresti: if (timeout.HasValue) invece di un confronto diretto con null.
Matt H

2
Null non è peggio del numero magico. Con i numeri magici, non sai mai quale sia il numero magico ... potrebbe essere 0, -1 o qualcos'altro. null è solo null.
marco-fiset,

9
Null significa assenza di valore. Questo è il concetto che molti numeri magici intendono esprimere. Essere in grado di utilizzare null con un tipo nullable è una soluzione MOLTO migliore rispetto alla selezione di un valore arbitrario dall'intervallo di valori possibili per un tipo di dati.
17 del 26

2
Usare "null" come valore magico, se il tuo tipo ha "null", va bene. L'importante è NOMINARLO, perché sicuramente sparare al prossimo che verrà non saprà cosa intendevi. Null può significare "infinito", "non ancora specificato", "bug nel codice che ha creato la struttura dei dati" o qualsiasi altro numero di cose. Solo un nome consente al programmatore successivo di sapere che intendevi che quel valore fosse presente e quale comportamento intendevi attivare.
mjfgates,

1
@CodeInChaos: So che puoi fare entrambe le cose, ma preferisco HasValue. In realtà non sono un grande fan di null in generale, ma i tipi nullable che usano HasValue sono un po 'più vicini a un'opzione / forse a me, di cui sono un fan.
Matt H

10

MAGIC_NUMBERil codice dovrebbe assolutamente essere sempre evitato, ove possibile. nullè un'espressione molto più chiara di intenti.


6

In C #, molte classi CLR hanno un Emptymembro 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 un int? In tal caso, considera se stai cadendo vittima dell'ossessione primitiva . È del tutto possibile che la tua proprietà numerica apparentemente semplice trarrebbe beneficio dalla propria classe o struttura, il che ti consentirebbe di specificare il Emptymembro e aggiungere anche altri comportamenti specifici a quel tipo di valore.


3

In questo caso, il valore null è un ottimo modo per indicare che non esiste un massimo. Generalmente quando il caso speciale indica che il valore in questione non si applica, che semplicemente non si desidera la funzionalità che configura, null è una buona indicazione di ciò.

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


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 un valore di 0.

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


1

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 nell'inizializzazione.

Nel contesto del problema, la timeoutInMillisecondsproprietà è facoltativa e non si fa menzione del fatto che l'overhead di questo approccio lo squalificherebbe come opzione.

Conclusione: ci sono eccezioni e le soluzioni variano in base alla lingua e al dominio; in questo caso, sceglierei Null. Dove (credo) alcune persone sbagliano è quando non separano bene i dati dall'interfaccia. Si aspettano solo che qualsiasi client legga la documentazione (o l'implementazione) per determinare come questi valori speciali devono essere usati / gestiti - i casi speciali perdono nel programma del client e può essere poco chiaro. Aggiungendo un buon livello di astrazione, l'utilizzo può essere molto più chiaro.


0

Null è peggio da usare rispetto a MagicNumber. Null rappresenta l'idea espressa meglio, ma non è coerente su tutte le piattaforme nel modo in cui si comporta, l'utilizzo MagicNumberfunziona sempre allo stesso modo 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.


0

Se dimentichi di controllare il numero magico (succederà nel modo giusto), un numero magico continuerà per un po 'con dati privi di senso. Molto meglio avere un null che provoca un'eccezione il prima possibile.


-1

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

public static int NO_TIMEOUT = 0;  // javaish

Null è male. Nell'esempio sopra potresti essere in grado di cavartela poiché il codice sarebbe ovviamente in grado di gestire un valore nullo. Ma in generale quello che succede quando inizi a passare null è che prima o poi ricevi un'eccezione puntatore null. Potrebbe non accadere quando si scrive il codice per la prima volta, ma il codice viene mantenuto per molto più tempo rispetto alla prima versione. È spesso gestito da persone che non sanno molto del sistema come gli sviluppatori originali.

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

Ciò rende ovvio a qualsiasi sviluppatore che potrebbe non esserci un valore e che tu abbia un codice migliore per quello. Bene, dovrebbe renderlo ovvio comunque.

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


4
-1: Giustifica "null is evil".
Deworde,

4
La definizione di un alias per un numero non cambia il fatto che sia ancora un numero magico.
17 del 26

Bene, forse hai una definizione diversa di numero magico rispetto a me. Si prega di consultare en.wikipedia.org/wiki/…
Jon Strayer il

1
Sono d'accordo con Jon Strayer qui. Null è un esempio di ADT in una lingua che in realtà non supporta ADT. L'OP probabilmente può cavarsela qui, ma in generale considero che qualsiasi linguaggio che ha null abbia fallito, è leggermente programmatore.
Jeremy Wall
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.