Ogni numero nel codice è considerato un "numero magico"?

21

Quindi ogni numero nel codice che stiamo inviando a un metodo come argomento è considerato un numero magico? Per me, non dovrebbe. Penso che se un numero è diciamo che è per la lunghezza minima del nome utente e iniziamo a usare "6" nel codice ... allora sì abbiamo un problema di manutenzione e qui "6" è un numero magico .... ma se chiamiamo un metodo che uno dei suoi argomenti accetta un numero intero come membro di una collezione e quindi passiamo "0" a quella chiamata di metodo, in questo caso non vedo che "0" come una magia numero. Cosa ne pensi?

    
posta Blake 08.01.2013 - 19:27
fonte

5 risposte

43

Se il significato del numero è molto chiaro nel contesto, non penso che sia un problema di "numero magico".

Esempio: diciamo che stai cercando di ottenere la sottostringa di una stringa, dall'inizio ad alcuni token e il codice è simile a questo (linguaggio e libreria immaginari):

s := substring(big_string, 0, findFirstOccurence(SOME_TOKEN, big_string));

In questo contesto, il significato del numero 0 è abbastanza chiaro. Suppongo che potresti definire START_OF_SUBSTRING e impostarlo su 0, ma in questo caso penso che sarebbe eccessivo (anche se sarebbe l'approccio corretto se tu sapessi che l'inizio della sottostringa potrebbe non essere 0, ma che dipende da le specifiche della tua situazione).

Un altro esempio potrebbe essere se stai cercando di determinare se un numero è pari o dispari. Scrittura:

isEven := x % 2;

non è così strano come:

TWO := 2;
isEven := x % TWO;

Test dei numeri negativi come

MINUS_ONE := -1;
isNegativeInt := i <= MINUS_ONE;

mi sembra strano, preferirei vedere

isNegativeInt := i <= -1;
    
risposta data 08.01.2013 - 19:36
fonte
17
bool hasApples = apples > 0;

È ovvio che lo zero significa assenza. Trovo 0 più facile da capire rispetto a una variabile denominata "assenzaValore".


for(int i=0; i < arr.length; i++)

È ovvio che 0 è la posizione di partenza. Sarei confuso da una variabile denominata "firstPosition". Una tale variabile mi farebbe chiedere se la posizione iniziale potrebbe cambiare.

    
risposta data 08.01.2013 - 20:01
fonte
14

Suggerirei tre fattori chiave nel decidere se qualcosa dovrebbe essere una dichiarazione costante:

  1. Il numero è qualcosa che è rappresentato in modo preciso e conciso
  2. Ci sono scenari plausibili in base ai quali il valore dovrebbe cambiare, ma il codice non dovrebbe essere riscritto
  3. Qualcuno che vede il numero dovrebbe riconoscerlo più rapidamente o meno rapidamente di qualcuno che vede una costante nominata

Qualcosa come pi dovrebbe probabilmente essere scritto come costante nominata, piuttosto che come valore letterale numerico, dal momento che un valore letterale numerico può essere inutilmente prolisso, inutilmente impreciso o entrambi. Qualcosa come il numero di slot in una cache dovrebbe probabilmente essere una costante denominata (sebbene si veda la nota sotto) per consentire la possibilità di espandere la cache senza dover modificare tutto il codice che la utilizza. Cose come i numeri "4", "28" e "29" nell'istruzione if ((year % 4)==0) FebruaryDays = 29; else FebruaryDays = 28; probabilmente non dovrebbero essere denominate costanti, poiché l'espressione è quasi certamente più leggibile di if ((year % YearsBetweenLeapYears)==0) FebruaryDays = FebruaryDaysInLeapYear; else FebruaryDays = FebruaryDaysInNonLeapYear; . Si noti che i manutentori degli standard hanno indicato che la lunghezza del 2100 febbraio in quell'anno non corrisponderà alla formula precedente, ma raccomanderei non di gestire quel caso a meno che non sia il solo em> impedimento alla corretta gestione di tali date (ovvero il codice non verrà attivato dall'overflow dei numeri interi o da altri problemi simili).

Un avvertimento importante con la regola n. 2 è che in alcuni casi il codice può fare affidamento su numeri hard-coded in un modo che non può essere facilmente rappresentato da una costante nominata. Ad esempio, un metodo che calcola un prodotto incrociato di due vettori passati come parametri discreti sarà significativo solo se utilizzato su vettori tridimensionali. Il numero richiesto di dimensioni non è un valore che potrebbe essere modificato in modo significativo senza riscrivere completamente la routine. Anche se si prevedesse un possibile bisogno di calcolare il prodotto incrociato di tre vettori a 4 dimensioni, usare una costante nominata per il valore "3" farebbe ben poco per rendere più semplice soddisfare tale esigenza.

    
risposta data 08.01.2013 - 22:07
fonte
4

Questo, come tutti i principi, è una questione di gradi. In generale, i numeri letterali nel codice sorgente sono più sospetti, più grandi sono. Una lunghezza massima pari a 10 o un indirizzo di memoria come 0x587FB0 sono ovviamente una cattiva pratica - è quasi certo che prima o poi dovrai ripetere questi valori più di una volta, creando un rischio di incompatibilità e errori sottili introdotti in luoghi che non erano cambiato.

0 è all'altra estremità della scala; è ancora sospetto ma non così tanto. Stai usando 0 come valore sentinella? Quindi dovresti probabilmente usare una costante simbolica, solo perché la costante può spiegare cosa significa. Si tratta di un accordo culturale estremamente radicato come "0 significa completamento riuscito"? Questo è probabilmente OK. Significa "il primo oggetto di una collezione"? Potrebbe essere innocuo, ma se c'è un metodo alternativo come first() probabilmente lo preferirei.

    
risposta data 08.01.2013 - 19:39
fonte
3

Ogni numero senza nome che non è immediatamente evidente dal contesto è un numero magico. È un po 'sciocco definire i numeri che hanno un significato immediatamente evidente dal contesto.

In django (framework web python), posso definire alcuni campi del database con un numero raw come:

firstname = models.CharField(max_length=40)
middlename = models.CharField(max_length=40)
lastname =  models.CharField(max_length=40) 

che è più chiaro (e la pratica consigliata ) che dire

MAX_LENGTH_NAME = 40
...
firstname = models.CharField(max_length=MAX_LENGTH_NAME)
middlename = models.CharField(max_length=MAX_LENGTH_NAME)
lastname =  models.CharField(max_length=MAX_LENGTH_NAME) 

perché è improbabile che abbia mai bisogno di modificare la lunghezza (e può sempre comparare con il max_length del campo). Se devo modificare la lunghezza del campo dopo aver inizialmente distribuito l'applicazione, ho bisogno di cambiarlo esattamente in una posizione per ogni campo nel mio codice django, e quindi di scrivere anche una migrazione per cambiare lo schema del DB. Se dovessi mai fare riferimento a max_length di un campo definito di un tipo di oggetto, posso farlo direttamente - se quei campi stavano definendo una classe Person , posso usare Person._meta.get_field('firstname').max_length per ottenere il max_length essendo usato (che è definito in un posto). Il fatto che lo stesso 40 sia stato utilizzato per più campi è irrilevante in quanto potrei volerli cambiare in modo indipendente. La lunghezza del nome non dovrebbe mai dipendere dalla lunghezza di middlename o lastname; sono valori separati e possono cambiare indipendentemente.

Spesso gli indici di array possono usare numeri senza nome; come se avessi un file CSV di dati che voglio mettere in un dizionario python, con il primo elemento della riga come il dizionario key vorrei scrivere:

mydict = {}
for row in csv.reader(f):
    mydict[row[0]] = row[1:]

Certo, potrei chiamare index_column = 0 e fare qualcosa del tipo:

index_col = 0
mydict = {}
for row in csv.reader(f):
    mydict[row[index_col]] = row[:index_col] + row[index_col+1:]

o peggio definisci after_index_col = index_col + 1 per eliminare index_col+1 , ma ciò non rende il codice più chiaro nella mia vista. Inoltre, se fornisco il index_col di un nome, è meglio che il codice funzioni anche se la colonna non è 0 (quindi la parte row[:index_col] + ).

    
risposta data 08.01.2013 - 23:53
fonte

Leggi altre domande sui tag