Eliminare i numeri magici: quando è il momento di dire "No"?


36

Siamo tutti consapevoli che i numeri magici (valori hardcoded) possono creare scompiglio nel tuo programma, soprattutto quando è il momento di modificare una sezione di codice che non ha commenti, ma dove traccia la linea?

Ad esempio, se si dispone di una funzione che calcola il numero di secondi tra due giorni, si sostituisce

seconds = num_days * 24 * 60 * 60

con

seconds = num_days * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE

A che punto decidi che è del tutto evidente cosa significa il valore hardcoded e lasciarlo da solo?


2
Perché non sostituire quel calcolo con una funzione o una macro in modo che il tuo codice finisca per apparireseconds = CALC_SECONDS(num_days);
FrustratedWithFormsDesigner

15
TimeSpan.FromDays(numDays).Seconds;
Nessuno il

18
@oosterwal: Con questo atteggiamento ( HOURS_PER_DAY will never need to be altered), non codificherai mai per il software distribuito su Marte. : P
FrustratedWithFormsDesigner

23
Avrei ridotto il numero di costanti a SECONDS_PER_DAY = 86400. Perché calcolare qualcosa che non cambierà?
JohnFx

17
Che dire dei secondi bisestili?
Giovanni

Risposte:


40

Esistono due motivi per utilizzare le costanti simboliche anziché i letterali numerici:

  1. Per semplificare la manutenzione se i numeri magici cambiano. Questo non si applica al tuo esempio. È estremamente improbabile che il numero di secondi in un'ora o il numero di ore in un giorno cambierà.

  2. Per migliorare la leggibilità. L'espressione "24 * 60 * 60" è abbastanza ovvia per quasi tutti. Anche "SECONDS_PER_DAY" lo è, ma se stai cercando un bug, potresti dover controllare che SECONDS_PER_DAY sia stato definito correttamente. C'è valore in breve.

Per i numeri magici che appaiono esattamente una volta e sono indipendenti dal resto del programma, decidere se creare un simbolo per quel numero è una questione di gusti. In caso di dubbi, andare avanti e creare un simbolo.

Non farlo:

public static final int THREE = 3;

3
+1 @kevin cline: sono d'accordo con il tuo punto sulla brevità di una caccia ai bug. Il vantaggio aggiuntivo che vedo nell'uso di una costante con nome, soprattutto durante il debug, è che se si scopre che una costante è stata definita in modo errato, è necessario modificare solo un pezzo di codice anziché cercare in un intero progetto tutte le occorrenze di implementate in modo errato valore.
Oosterwal,

40
O peggio ancora:publid final int FOUR = 3;
gablin,

3
Oh caro, devi aver lavorato con lo stesso ragazzo con cui una volta ho lavorato.
quick_now

2
@gablin: Per essere onesti, è abbastanza utile che i pub abbiano dei coperchi.
Alan Pearce,

10
Ho visto questo: public static int THREE = 3;... nota - no final!
Stephen C,

29

Terrei la regola di non avere mai numeri magici.

Mentre

seconds = num_days * 24 * 60 * 60

È perfettamente leggibile per la maggior parte del tempo, dopo aver programmato 10 ore al giorno per tre o quattro settimane in modalità crunch

seconds = num_days * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE

è molto più facile da leggere.

FrustratedWithFormsIl suggerimento di Designer è migliore:

seconds = num_days * DAYS_TO_SECOND_FACTOR

o anche meglio

seconds = CONVERT_DAYS_TO_SECONDS(num_days)

Le cose smettono di essere ovvie quando sei molto stanco. Codice difensivo .


13
Entrare in una modalità crunch come la descrivi è un antipattern controproducente che dovrebbe essere evitato. I programmatori raggiungono la massima produttività sostenuta a circa 35-40 ore / settimana.
btilly

4
@btilly sono pienamente d'accordo con te. Ma succede, spesso a causa di fattori esterni.
Vitor Py

3
Definisco in generale costanti per secondo, minuto, giorno e ora. Se non altro '30 * MINUTE 'è davvero facile da leggere e so che è un momento senza doverci pensare.
Zachary K

9
@btilly: il picco è di 35-40 ore o un livello di alcol nel sangue tra 0,129% e 0,138%. L'ho letto su XKCD , quindi deve essere vero!
Oosterwal,

1
Se vedessi una costante come HOURS_PER_DAY, la cancellerei e poi ti umiliare pubblicamente davanti ai tuoi pari. Ok, forse avrei rinunciato all'umiliazione pubblica, ma probabilmente lo eliminerei.
Ed S.

8

Il tempo di dire di no è quasi sempre. Le volte in cui trovo che sia più semplice utilizzare solo numeri con codice fisso si trovano in luoghi come il layout dell'interfaccia utente: la creazione di una costante per il posizionamento di ogni controllo sul modulo diventa molto complessa e stancante e se quel codice viene solitamente gestito da un designer dell'interfaccia utente non ha molta importanza. ... a meno che l'interfaccia utente non sia strutturata in modo dinamico, o utilizzi posizioni relative a un punto di ancoraggio o sia scritta a mano. In tal caso, direi che è meglio definire alcune costanti significative per il layout. E se hai bisogno di un fattore di sfumatura qua o là per allineare / posizionare qualcosa di "giusto", anche questo dovrebbe essere definito.

Ma nel tuo esempio, penso che sostituire 24 * 60 * 60con DAYS_TO_SECONDS_FACTORsia meglio.


Ammetto che anche i valori codificati sono OK quando il contesto e l'uso sono completamente chiari. Questo, tuttavia, è un giudizio ...

Esempio:

Come sottolineato da @rmx, l'uso di 0 o 1 per verificare se un elenco è vuoto, o forse nei limiti di un ciclo, è un esempio di un caso in cui lo scopo della costante è molto chiaro.


2
Di solito è OK da usare 0o 1credo. if(someList.Count != 0) ...è meglio di if(someList.Count != MinListCount) .... Non sempre, ma in generale.
Nessuno il

2
@Dima: VS Form Designer gestisce tutto ciò. Se vuole creare costanti, va bene per me. Ma non entrerò nel codice generato e sostituirò tutti i valori codificati con costanti.
FrustratedWithFormsDesigner

4
Non confondiamo il codice che deve essere generato e gestito da uno strumento con codice scritto per il consumo umano.
biziclop

1
@FrustratedWithFormsDesigner come sottolineato da @biziclop, il codice generato è un animale completamente diverso. Le costanti nominate devono assolutamente essere utilizzate nel codice letto e modificato dalle persone. Il codice generato, almeno nel caso ideale, non dovrebbe essere modificato affatto.
Dima,

2
@FrustratedWithFormsDesigner: cosa succede quando hai un valore noto hard-coded in dozzine di file nel tuo programma che improvvisamente deve essere cambiato? Ad esempio, si codifica il valore che rappresenta il numero di tick di clock per microsecondo per un processore incorporato, quindi viene richiesto di eseguire il porting del software su un progetto in cui esiste un numero diverso di tick di clock per microsecondo. Se il tuo valore fosse stato qualcosa di comune, come 8, eseguire una ricerca / sostituzione su decine di file potrebbe finire per causare più problemi.
Oosterwal,

8

Interrompi quando non è possibile definire un significato o uno scopo per il numero.

seconds = num_days * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE

è molto più facile da leggere che usare semplicemente i numeri. (Anche se potrebbe essere reso più leggibile con una sola SECONDS_PER_DAYcostante, ma è un problema completamente separato.)

Supponiamo che uno sviluppatore guardando il codice possa vedere cosa fa. Ma non dare per scontato che sappiano anche perché. Se la tua costante aiuta a capire il perché, provaci. Altrimenti no.

Se finisci con troppe costanti, come suggerito da una risposta, considera invece l'uso di un file di configurazione esterno, poiché avere dozzine di costanti in un file non migliora esattamente la leggibilità.


7

Probabilmente direi "no" a cose come:

#define HTML_END_TAG "</html>"

E sicuramente direi "no" a:

#define QUADRATIC_DISCRIMINANT_COEF 4
#define QUADRATIC_DENOMINATOR_COEF  2

7

Uno dei migliori esempi che ho trovato per promuovere l'uso delle costanti per cose ovvie come HOURS_PER_DAYè:

Stavamo calcolando per quanto tempo le cose erano rimaste nella fila di lavoro di una persona. I requisiti sono stati definiti in modo approssimativo e il programmatore è stato codificato 24in numerosi punti. Alla fine ci siamo resi conto che non era giusto punire gli utenti per aver affrontato un problema per 24 ore, quando in realtà funzionano solo 8 ore al giorno. Quando è venuto il compito di risolvere questo problema E vedere quali altri report potrebbero avere lo stesso problema, era abbastanza difficile grep / cercare nel codice per 24 sarebbe stato molto più facile grep / cercareHOURS_PER_DAY


oh no, quindi le ore al giorno variano a seconda che tu faccia riferimento alle ore di lavoro al giorno (lavoro 7,5 BTW) o alle ore in un giorno. Per cambiare il significato della costante in questo modo, ti consigliamo di sostituirne il nome con qualcos'altro. Il tuo punto di ricerca reso semplice è valido però.
gbjbaanb,

2
Sembra che in questo caso HOURS_PER_DAY non fosse nemmeno la costante desiderata. Ma essere in grado di cercarlo per nome è un enorme vantaggio, anche se (o soprattutto se) è necessario cambiarlo in qualcos'altro in molti luoghi.
David K,

4

Penso che finché il numero è completamente costante e non ha possibilità di modifica, è perfettamente accettabile. Quindi, nel tuo caso, seconds = num_days * 24 * 60 * 60va bene (supponendo ovviamente che tu non faccia qualcosa di stupido come fare questo tipo di calcolo all'interno di un ciclo) e probabilmente migliore per leggibilità di seconds = num_days * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE.

È quando fai cose del genere che è male:

lineOffset += 24; // 24 lines to a page

Anche se non puoi più inserire righe nella pagina o anche se non hai intenzione di cambiarla, usa invece una variabile costante, perché un giorno tornerà a perseguitarti. In definitiva, il punto è la leggibilità, non risparmiando 2 cicli di calcolo sulla CPU. Questo non è più il 1978 quando i preziosi byte furono spremuti per tutto il loro valore.


2
Troverebbe accettabile codificare il valore immutabile 86400 invece di utilizzare la costante denominata SECONDS_PER_DAY? Come verifichi che tutte le occorrenze del valore erano corrette e non mancavano uno 0 o scambiavano il 6 ad 4, ad esempio?
Oosterwal,

Allora perché no: secondi = num_days * 86400? Neanche questo cambierà.
JeffO

2
Ho fatto la cosa SECONDS_PER_DAY in entrambi i modi: usando il nome e usando il numero. Quando torni al codice 2 anni dopo, il numero indicato SEMPRE ha più senso.
quick_now

2
secondi = num_days * 86400 non mi è chiaro. Questo è ciò che alla fine conta. Se vedo "secondi = num_days * 24 * 60 * 60", a parte il fatto che i nomi delle variabili danno il significato abbastanza bene in questo caso, mi chiedo immediatamente perché li ho separati e il significato diventa ovvio perché ho lasciato come numeri (quindi sono costanti), non variabili per le quali ulteriori indagini mi richiederebbero di capire i loro valori e se sono costanti.
Neil

1
Ciò che la gente spesso non capisce: se modifichi quel valore di lineOffset da 24 a 25, dovrai esaminare tutto il codice per vedere dove è stato utilizzato 24 e se deve essere modificato, quindi tutti i calcoli da giorni a ore moltiplicando per 24 davvero mettiti sulla tua strada.
gnasher729,

3
seconds = num_days * 24 * 60 * 60

Va benissimo. Questi non sono numeri davvero magici in quanto non cambieranno mai.

Qualsiasi numero che può ragionevolmente cambiare o non avere un significato ovvio dovrebbe essere messo in variabili. Il che significa praticamente tutti.


2
Sarebbe seconds = num_days * 86400ancora accettabile? Se un valore del genere fosse utilizzato più volte in molti file diversi, come verificherebbe che qualcuno non ha digitato accidentalmente seconds = num_days * 84600in uno o due punti?
Oosterwal,

1
Scrivere 86400 è molto diverso da scrivere 24 * 60 * 60.
Carra

4
Certamente cambierà. Non tutti i giorni hanno 86.400 secondi. Si consideri, ad esempio, l'ora legale. Una volta all'anno, alcuni posti hanno solo 23 ore al giorno e un altro giorno ne avranno 25. Poof i tuoi numeri sono rotti.
Dave DeLong

1
@Dave, punto eccellente. Esistono secondi di salto - it.wikipedia.org/wiki/Leap_second

Punto valido. L'aggiunta di una funzione sarebbe una protezione se mai dovessi cogliere queste eccezioni.
Carra

3

Eviterei di creare costanti (valori magici) per convertire un valore da un'unità all'altra. In caso di conversione preferisco un nome metodo di parlare. In questo esempio questo sarebbe ad esempio DayToSeconds(num_days)interno, il metodo non ha bisogno di valori magici perché il significato di "24" e "60" è chiaro.

In questo caso non userei mai secondi / minuti / ore. Vorrei usare solo TimeSpan / DateTime.


1

Usa il contesto come parametro per decidere

Ad esempio, hai una funzione chiamata "calcolaSecondi tra: a Giorno e: un altro giorno", non dovrai fare molta esaplanazione su ciò che fanno quei numeri, perché il nome della funzione è piuttosto rappresentativo.

E un'altra domanda è: quali sono le possibilità per calcolarlo in modo diverso? A volte ci sono molti modi per fare la stessa cosa, quindi per guidare i futuri programmatori e mostrare loro quale metodo hai usato, definire le costanti potrebbe aiutarti a capirlo.

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.