Cos'è un numero magico?
Perché dovrebbe essere evitato?
Ci sono casi in cui è appropriato?
Cos'è un numero magico?
Perché dovrebbe essere evitato?
Ci sono casi in cui è appropriato?
Risposte:
Un numero magico è un uso diretto di un numero nel codice.
Ad esempio, se hai (in Java):
public class Foo {
public void setPassword(String password) {
// don't do this
if (password.length() > 7) {
throw new InvalidArgumentException("password");
}
}
}
Questo dovrebbe essere refactored per:
public class Foo {
public static final int MAX_PASSWORD_SIZE = 7;
public void setPassword(String password) {
if (password.length() > MAX_PASSWORD_SIZE) {
throw new InvalidArgumentException("password");
}
}
}
Migliora la leggibilità del codice ed è più facile da mantenere. Immagina il caso in cui ho impostato la dimensione del campo password nella GUI. Se uso un numero magico, ogni volta che cambia la dimensione massima, devo cambiare in due posizioni di codice. Se ne dimentico uno, questo porterà a incoerenze.
Il JDK è piena di esempi come in Integer
, Character
e Math
le classi.
PS: strumenti di analisi statica come FindBugs e PMD rilevano l'uso di numeri magici nel tuo codice e suggeriscono il refactoring.
TRUE
/ FALSE
)
Un numero magico è un valore codificato che può cambiare in un secondo momento, ma che può quindi essere difficile da aggiornare.
Ad esempio, supponiamo che tu abbia una Pagina che mostra gli ultimi 50 ordini in una pagina di riepilogo "I tuoi ordini". 50 è il numero magico qui, perché non è impostato tramite standard o convenzione, è un numero che hai inventato per i motivi indicati nelle specifiche.
Ora, quello che fai è che hai i 50 in luoghi diversi: il tuo script SQL ( SELECT TOP 50 * FROM orders
), il tuo sito Web (i tuoi ultimi 50 ordini), il tuo login dell'ordine ( for (i = 0; i < 50; i++)
) e possibilmente molti altri luoghi.
Ora, cosa succede quando qualcuno decide di cambiare da 50 a 25? o 75? o 153? Ora devi sostituire il 50 in tutti i posti e molto probabilmente ti mancherà. Trova / Sostituisci potrebbe non funzionare, perché 50 potrebbe essere utilizzato per altre cose e la sostituzione alla cieca di 50 con 25 può avere altri effetti collaterali negativi (ad esempio, la tua Session.Timeout = 50
chiamata, che è anche impostata su 25 e gli utenti iniziano a segnalare timeout troppo frequenti).
Inoltre, il codice può essere difficile da capire, ad esempio " if a < 50 then bla
" - se lo si incontra nel mezzo di una funzione complicata, altri sviluppatori che non hanno familiarità con il codice potrebbero chiedersi "WTF è 50 ???"
Ecco perché è meglio avere numeri così ambigui e arbitrari in esattamente 1 posto - " const int NumOrdersToDisplay = 50
", perché ciò rende il codice più leggibile (" if a < NumOrdersToDisplay
", significa anche che devi solo cambiarlo in 1 posto ben definito.
I luoghi in cui i numeri magici sono appropriati è tutto ciò che è definito attraverso uno standard, ovvero SmtpClient.DefaultPort = 25
o TCPPacketSize = whatever
(non sono sicuro che sia standardizzato). Inoltre, tutto ciò che è definito solo all'interno di 1 funzione potrebbe essere accettabile, ma ciò dipende dal contesto.
SmtpClient.DefaultPort = 25
è forse chiaro er rispetto SmtpClient.DefaultPort = DEFAULT_SMTP_PORT
.
25
tutta l'applicazione e assicurarti di modificare solo le occorrenze di 25
quelle per la porta SMTP, non i 25 che sono ad esempio la larghezza di una colonna della tabella o il numero di record da mostrare in una pagina.
IANA
.
Hai dato un'occhiata alla voce di Wikipedia per il numero magico?
Descrive in dettaglio tutti i modi in cui viene fatto il riferimento al numero magico. Ecco una citazione sul numero magico come una cattiva pratica di programmazione
Il termine numero magico si riferisce anche alla cattiva pratica di programmazione dell'utilizzo dei numeri direttamente nel codice sorgente senza spiegazione. Nella maggior parte dei casi ciò rende i programmi più difficili da leggere, comprendere e mantenere. Sebbene la maggior parte delle guide faccia un'eccezione per i numeri zero e uno, è una buona idea definire tutti gli altri numeri nel codice come costanti nominate.
Magia: semantico sconosciuto
Costante simbolica -> Fornisce sia il contesto semantico che quello corretto per l'uso
Semantico: il significato o lo scopo di una cosa.
"Crea una costante, chiamala in base al significato e sostituisci il numero con essa." - Martin Fowler
Innanzitutto, i numeri magici non sono solo numeri. Qualsiasi valore di base può essere "magico". I valori di base sono entità manifest come numeri interi, reali, doppi, float, date, stringhe, valori booleani, caratteri e così via. Il problema non è il tipo di dati, ma l'aspetto "magico" del valore come appare nel nostro testo in codice.
Cosa intendiamo per "magia"? Per essere precisi: per "magia", intendiamo indicare la semantica (significato o scopo) del valore nel contesto del nostro codice; che è sconosciuto, inconoscibile, poco chiaro o confuso. Questa è la nozione di "magia". Un valore di base non è magico quando il suo significato semantico o lo scopo dell'essere è rapidamente e facilmente conosciuto, chiaro e compreso (non confuso) dal contesto surround senza parole speciali di supporto (ad esempio costante simbolica).
Pertanto, identifichiamo i numeri magici misurando la capacità di un lettore di codice di conoscere, essere chiaro e comprendere il significato e lo scopo di un valore di base dal suo contesto circostante. Meno noto, meno chiaro e più confuso è il lettore, più "magico" è il valore di base.
Abbiamo due scenari per i nostri magici valori di base. Solo il secondo è di primaria importanza per programmatori e codice:
Una dipendenza generale della "magia" è come il valore di base solitario (ad esempio il numero) non abbia un semantico comunemente noto (come Pi), ma abbia un semantico conosciuto localmente (ad esempio il programma), che non è del tutto chiaro dal contesto o potrebbe essere abusato in contesti positivi o negativi.
La semantica della maggior parte dei linguaggi di programmazione non ci consentirà di utilizzare valori di base solitari, tranne (forse) come dati (ovvero tabelle di dati). Quando incontriamo "numeri magici", generalmente lo facciamo in un contesto. Pertanto, la risposta a
"Sostituisco questo numero magico con una costante simbolica?"
è:
"Quanto velocemente puoi valutare e comprendere il significato semantico del numero (il suo scopo di essere lì) nel suo contesto?"
Con questo pensiero in mente, possiamo rapidamente vedere come un numero come Pi (3.14159) non sia un "numero magico" se collocato nel contesto appropriato (ad esempio 2 x 3.14159 x raggio o 2 * Pi * r). Qui, il numero 3.14159 è mentalmente riconosciuto Pi senza l'identificatore simbolico costante.
Tuttavia, generalmente sostituiamo 3.14159 con un identificatore simbolico costante come Pi a causa della lunghezza e della complessità del numero. Gli aspetti di lunghezza e complessità di Pi (associati a una necessità di precisione) di solito indicano che l'identificatore simbolico o la costante sono meno inclini all'errore. Il riconoscimento di "Pi" come nome è semplicemente un vantaggio conveniente, ma non è il motivo principale per avere la costante.
Mettendo da parte le costanti comuni come Pi, concentriamoci principalmente su numeri con significati speciali, ma che tali significati sono vincolati all'universo del nostro sistema software. Tale numero potrebbe essere "2" (come valore intero di base).
Se uso il numero 2 da solo, la mia prima domanda potrebbe essere: cosa significa "2"? Il significato di "2" di per sé è sconosciuto e inconoscibile senza contesto, lasciando il suo uso poco chiaro e confuso. Anche se avere solo "2" nel nostro software non accadrà a causa della semantica del linguaggio, vogliamo vedere che "2" da solo non ha una semantica speciale o uno scopo ovvio essendo solo.
Mettiamo il nostro solitario "2" in un contesto di:, padding := 2
dove il contesto è un "contenitore GUI". In questo contesto il significato di 2 (come pixel o altra unità grafica) ci offre una rapida ipotesi della sua semantica (significato e scopo). Potremmo fermarci qui e dire che 2 va bene in questo contesto e non c'è nient'altro che dobbiamo sapere. Tuttavia, forse nel nostro universo software questa non è l'intera storia. C'è di più, ma "padding = 2" come contesto non può rivelarlo.
Supponiamo inoltre che 2 come pixel padding nel nostro programma sia della varietà "default_padding" in tutto il nostro sistema. Pertanto, scrivere le istruzioni padding = 2
non è abbastanza buono. La nozione di "default" non viene rivelata. Solo quando scrivo: padding = default_padding
come contesto e poi altrove: default_padding = 2
realizzo pienamente un significato migliore e più pieno (semantico e scopo) di 2 nel nostro sistema.
L'esempio sopra è abbastanza buono perché "2" da solo potrebbe essere qualsiasi cosa. Solo quando limitiamo l'intervallo e il dominio della comprensione al "mio programma" in cui 2 è parte default_padding
della "GUI UX" del "mio programma", possiamo finalmente dare un senso a "2" nel suo giusto contesto. Qui "2" è un numero "magico", che viene preso in considerazione in una costante simbolica default_padding
nel contesto della GUI UX del "mio programma" al fine di farne un uso il più default_padding
rapidamente compreso nel contesto più ampio del codice allegato.
Pertanto, qualsiasi valore di base, il cui significato (semantico e scopo) non può essere sufficientemente e rapidamente compreso, è un buon candidato per una costante simbolica al posto del valore di base (ad es. Numero magico).
Anche i numeri su una scala possono avere semantica. Ad esempio, fingiamo di creare un gioco D&D, in cui abbiamo l'idea di un mostro. Il nostro oggetto mostro ha una funzione chiamata life_force
, che è un numero intero. I numeri hanno significati che non sono conoscibili o chiari senza parole per fornire significato. Quindi, iniziamo dicendo arbitrariamente:
Dalle costanti simboliche sopra, iniziamo a ottenere un quadro mentale della vitalità, della morte e della "non morte" (e possibili ramificazioni o conseguenze) per i nostri mostri nel nostro gioco D&D. Senza queste parole (costanti simboliche), ci rimangono solo i numeri che vanno da -10 .. 10
. Solo l'intervallo senza le parole ci lascia in un luogo di grande confusione e potenzialmente con errori nel nostro gioco se diverse parti del gioco hanno dipendenze da ciò che quell'intervallo di numeri significa per varie operazioni come attack_elves
o seek_magic_healing_potion
.
Pertanto, quando cerchiamo e consideriamo la sostituzione di "numeri magici", vogliamo porre domande molto mirate sui numeri nel contesto del nostro software e persino su come i numeri interagiscono semanticamente tra loro.
Rivediamo quali domande dovremmo porre:
Potresti avere un numero magico se ...
Esaminare i valori di base costanti manifest autonomi nel testo del codice. Poni ogni domanda lentamente e con attenzione su ogni istanza di tale valore. Considera la forza della tua risposta. Molte volte, la risposta non è in bianco e nero, ma ha sfumature di significato e scopo incompresi, velocità di apprendimento e velocità di comprensione. C'è anche la necessità di vedere come si collega alla macchina software che lo circonda.
Alla fine, la risposta alla sostituzione è rispondere alla misura (nella tua mente) della forza o debolezza del lettore per stabilire la connessione (ad es. "Ottenerlo"). Più rapidamente capiscono significato e scopo, meno "magia" hai.
CONCLUSIONE: Sostituisci i valori di base con costanti simboliche solo quando la magia è abbastanza grande da causare difficoltà a rilevare bug derivanti da confusioni.
Un numero magico è una sequenza di caratteri all'inizio di un formato file o scambio di protocollo. Questo numero funge da controllo di integrità.
Esempio: apri qualsiasi file GIF, vedrai all'inizio: GIF89. "GIF89" è il numero magico.
Altri programmi possono leggere i primi caratteri di un file e identificare correttamente le GIF.
Il pericolo è che i dati binari casuali possano contenere questi stessi caratteri. Ma è molto improbabile.
Per quanto riguarda lo scambio di protocollo, è possibile utilizzarlo per identificare rapidamente che l'attuale "messaggio" che viene passato all'utente è danneggiato o non valido.
I numeri magici sono ancora utili.
Nella programmazione, un "numero magico" è un valore a cui dovrebbe essere assegnato un nome simbolico, ma è stato invece inserito nel codice come letterale, di solito in più di un posto.
È negativo per lo stesso motivo per cui SPOT (Single Point of Truth) è buono: se si desidera modificare questa costante in un secondo momento, è necessario cercare il codice per trovare ogni istanza. È anche negativo perché potrebbe non essere chiaro agli altri programmatori cosa rappresenta questo numero, quindi la "magia".
Le persone a volte prendono ulteriormente l'eliminazione dei numeri magici, spostando queste costanti in file separati per fungere da configurazione. Questo a volte è utile, ma può anche creare più complessità di quanto valga la pena.
(foo[i]+foo[i+1]+foo[i+2]+1)/3
può essere valutata molto più velocemente di un ciclo. Se uno dovesse sostituire il 3
senza riscrivere il codice come un ciclo, qualcuno che vedeva ITEMS_TO_AVERAGE
definito come 3
potrebbe immaginare di poterlo cambiare 5
e avere il codice in media più elementi. Al contrario, qualcuno che guardasse l'espressione con il letterale si 3
sarebbe reso conto che 3
rappresenta il numero di elementi che vengono sommati.
Un numero magico può anche essere un numero con una semantica speciale codificata. Ad esempio, una volta ho visto un sistema in cui gli ID record> 0 venivano trattati normalmente, 0 stesso era "nuovo record", -1 era "questa è la radice" e -99 era "questa è stata creata nella radice". 0 e -99 farebbero in modo che WebService fornisse un nuovo ID.
La cosa negativa di questo è che stai riutilizzando uno spazio (quello di numeri interi con segno per ID record) per abilità speciali. Forse non vorrai mai creare un record con ID 0 o con un ID negativo, ma anche in caso contrario, ogni persona che guarda il codice o il database potrebbe inciampare su questo e potrebbe essere inizialmente confusa. Va da sé che quei valori speciali non erano ben documentati.
Probabilmente, 22, 7, -12 e 620 contano anche come numeri magici. ;-)
Un problema che non è stato menzionato con l'uso dei numeri magici ...
Se ne hai moltissime, le probabilità sono ragionevolmente buone di avere due scopi diversi per i quali stai usando numeri magici, in cui i valori sembrano essere gli stessi.
E poi, abbastanza sicuro, è necessario modificare il valore ... per un solo scopo.
Presumo che questa sia una risposta alla mia risposta alla tua domanda precedente. Nella programmazione, un numero magico è una costante numerica incorporata che appare senza spiegazione. Se appare in due posizioni distinte, può portare a circostanze in cui un'istanza viene modificata e non un'altra. Per entrambi questi motivi, è importante isolare e definire le costanti numeriche al di fuori dei luoghi in cui vengono utilizzate.
Ho sempre usato il termine "numero magico" in modo diverso, come un valore oscuro memorizzato in una struttura di dati che può essere verificato come un rapido controllo di validità. Ad esempio i file gzip contengono 0x1f8b08 come i loro primi tre byte, i file di classe Java iniziano con 0xcafebabe, ecc.
Spesso vedi numeri magici incorporati nei formati di file, perché i file possono essere inviati in modo piuttosto promiscuo e perdere qualsiasi metadata su come sono stati creati. Tuttavia, i numeri magici vengono talvolta utilizzati anche per strutture di dati in memoria, come le chiamate ioctl ().
Un rapido controllo del numero magico prima dell'elaborazione del file o della struttura dei dati consente di segnalare tempestivamente gli errori, piuttosto che schlep fino in fondo attraverso un'elaborazione potenzialmente lunga al fine di annunciare che l'input era balderdash completo.
Vale la pena notare che a volte nel proprio codice si desiderano numeri "hardcoded" non configurabili. Ce ne sono alcuni famosi tra cui 0x5F3759DF che viene utilizzato nell'algoritmo di radice quadrata inversa ottimizzato.
Nei rari casi in cui trovo la necessità di utilizzare tali numeri magici, li imposto come const nel mio codice e documento il motivo per cui vengono utilizzati, come funzionano e da dove provengono.
Che dire di inizializzare una variabile nella parte superiore della classe con un valore predefinito? Per esempio:
public class SomeClass {
private int maxRows = 15000;
...
// Inside another method
for (int i = 0; i < maxRows; i++) {
// Do something
}
public void setMaxRows(int maxRows) {
this.maxRows = maxRows;
}
public int getMaxRows() {
return this.maxRows;
}
In questo caso, 15000 è un numero magico (secondo CheckStyles). Per me, l'impostazione di un valore predefinito è ok. Non voglio fare:
private static final int DEFAULT_MAX_ROWS = 15000;
private int maxRows = DEFAULT_MAX_ROWS;
Ciò rende più difficile la lettura? Non l'ho mai considerato fino a quando non ho installato CheckStyles.
static final
costanti siano eccessive quando le stai usando in un metodo. Una final
variabile dichiarata all'inizio del metodo è IMHO più leggibile.
@ eed3si9n: suggerirei persino che "1" è un numero magico. :-)
Un principio correlato ai numeri magici è che ogni fatto trattato dal tuo codice dovrebbe essere dichiarato esattamente una volta. Se usi numeri magici nel tuo codice (come l'esempio di lunghezza della password fornito da @marcio, puoi facilmente finire per duplicare quel fatto, e quando capisci che quel fatto cambia hai un problema di manutenzione.
factorial n = if n == BASE_CASE then BASE_VALUE else n * factorial (n - RECURSION_INPUT_CHANGE); RECURSION_INPUT_CHANGE = 1; BASE_CASE = 0; BASE_VALUE = 1
Che dire delle variabili di ritorno?
Lo trovo particolarmente difficile quando si implementano le procedure memorizzate .
Immagina la prossima procedura memorizzata (sintassi errata, lo so, solo per mostrare un esempio):
int procGetIdCompanyByName(string companyName);
Restituisce l'ID dell'azienda se esiste in una tabella particolare. Altrimenti, restituisce -1. In qualche modo è un numero magico. Alcuni dei consigli che ho letto finora dicono che dovrò davvero progettare qualcosa del genere:
int procGetIdCompanyByName(string companyName, bool existsCompany);
A proposito, cosa dovrebbe restituire se la società non esiste? Ok: imposterà esistesCompany come false , ma restituirà anche -1.
Un'altra opzione è fare due funzioni separate:
bool procCompanyExists(string companyName);
int procGetIdCompanyByName(string companyName);
Quindi una pre-condizione per la seconda procedura memorizzata è quella società esiste.
Ma ho paura della concorrenza, perché in questo sistema un'azienda può essere creata da un altro utente.
La linea di fondo a proposito è: cosa ne pensi di usare quel tipo di "numeri magici" che sono relativamente conosciuti e sicuri di dire che qualcosa non ha successo o che qualcosa non esiste?
Un altro vantaggio dell'estrazione di un numero magico come costante offre la possibilità di documentare chiaramente le informazioni aziendali.
public class Foo {
/**
* Max age in year to get child rate for airline tickets
*
* The value of the constant is {@value}
*/
public static final int MAX_AGE_FOR_CHILD_RATE = 2;
public void computeRate() {
if (person.getAge() < MAX_AGE_FOR_CHILD_RATE) {
applyChildRate();
}
}
}
const myNum = 22; const number = myNum / 11;
questo momento i miei 11 potrebbero essere persone o bottiglie di birra o qualcosa del genere, quindi invece cambierei 11 in una costante come gli abitanti.