Tutti i numeri magici sono creati uguali?


77

Su un recente progetto, avevo bisogno di convertire da byte a kilobyte kibibyte . Il codice era abbastanza semplice:

var kBval = byteVal / 1024;

Dopo averlo scritto, ho fatto funzionare il resto della funzione e sono passato.

Ma più tardi, ho iniziato a chiedermi se avevo appena inserito un numero magico nel mio codice. Una parte di me dice che andava bene perché il numero è una costante fissa e dovrebbe essere facilmente compreso. Ma un'altra parte di me pensa che sarebbe stato super chiaro se avvolta in una costante definita come BYTES_PER_KBYTE.

Quindi i numeri che sono costanti ben note sono davvero così magici o no?


Domande correlate:

Quando un numero è un numero magico? e ogni numero nel codice è considerato un "numero magico"? - sono simili, ma sono domande molto più ampie di quello che sto chiedendo. La mia domanda è focalizzata su numeri costanti ben noti che non sono affrontati in quelle domande.

Eliminare i numeri magici: quando è il momento di dire "No"? è anche correlato, ma si concentra sul refactoring anziché sul fatto che un numero costante sia o meno un numero magico.


17
Io in realtà lavorato su un progetto in cui avevano creato costanti come, FOUR_HUNDRED_FOUR = 404. Ho lavorato su un altro progetto in cui erano militanti sull'uso di stringhe costanti anziché letterali, quindi avevano dozzine di righe nel codice che sembravano,DATABASE = "database"
Rob

82
Sicuramente usalo 1024, perché altrimenti il ​​tuo team di sviluppo passerà tutto il tempo a discutere se si tratta di "kilobyte" o "kibibytes".
Gort the Robot

6
Potresti considerare che 1024 è kibi e #define KIBI1024, MEBIcome 1024 * 1024 ...
ysdx,

6
@Rob Y: sembra un buon vecchio programmatore Fortran. Perché quel linguaggio di programmazione ha costretto i programmatori a farlo. Sì, lì vedrai costanti come ZERO=0, ONE=1, TWO=2e quando i programmi sono portati in altre lingue (o i programmatori non cambiano comportamento quando cambiano la loro lingua) lo vedrai anche lì e devi pregare che nessuno lo cambi mai in ONE=2...
Holger,

4
@NoctisSkytower Il mio team preferisce utilizzare dichiarazioni di divisione esplicite anziché spostare gli operatori a spostamento di bit a causa delle molteplici lingue che utilizziamo e dell'implementazione potenzialmente incoerente in tali lingue. Allo stesso modo, i valori negativi vengono gestiti in modo incoerente con lo spostamento bit a bit. Sebbene potremmo non avere necessariamente valori di byte negativi, certamente abbiamo valori negativi con altre unità di misura che convertiamo.

Risposte:


103

Non tutti i numeri magici sono uguali.

Penso che in quel caso, quella costante sia OK. Il problema con i numeri magici è quando sono magici, cioè non è chiaro quale sia la loro origine, perché il valore è quello che è o se il valore è corretto o meno.

Nascondere 1024 dietro BYTES_PER_KBYTE significa anche che non vedi immediatamente se è corretto o meno.

Mi aspetto che qualcuno sappia immediatamente perché il valore è 1024. D'altra parte, se convertissi byte in megabyte, definirei la costante BYTES_PER_MBYTE o simile perché la costante 1.048.576 non è così ovvia che è 1024 ^ 2, oppure che è persino corretto.

Lo stesso vale per i valori dettati da requisiti o standard, che vengono utilizzati solo in un posto. Trovo che sia più semplice gestire la costante giusta con un commento alla fonte pertinente piuttosto che definirla altrove e dover inseguire entrambe le parti, ad esempio:

// Value must be less than 3.5 volts according to spec blah.
SomeTest = DataSample < 3.50

Trovo migliore di

SomeTest = DataSample < SOME_THRESHOLD_VALUE

Solo quando SOME_THRESHOLD_VALUEviene utilizzato in più punti vale il compromesso definire una costante, secondo me.


67
"Il problema con i numeri magici è quando sono magica" - Questo è ad esempio una spiegazione brillante di quel concetto! Sono serio! +1 solo per quella frase.
Jörg W Mittag,

20
Eccone uno che ho appena escogitato: "non è il numero il problema, è la magia".
Jörg W Mittag,

10
1024 è ovvio a chi? Non è questa la giustificazione per ogni numero magico? Tutti i numeri magici sono usati perché sono ovvi per chiunque li abbia scritti. 9.8 non è anche ovvio? Per me è abbastanza ovvio che è l'accelerazione della gravità sulla terra, ma creerei comunque una costante, perché ciò che è ovvio per me potrebbe non essere ovvio per qualcun altro.
Tulains Córdova,

15
No. Un commento come quello nel tuo esempio "migliore" è un'enorme bandiera rossa. È il codice che non supera nemmeno il test di leggibilità della persona che lo scrive al momento. Faccio un esempio. e^i*pi = -1è molto più esplicito (migliore) di 2.718^i*3.142 = -1. Le variabili contano e non sono solo per il codice comune. Il codice viene scritto per la prima lettura, compilando secondo. Inoltre, le specifiche cambiano (molto). Mentre il 1024 probabilmente non dovrebbe essere in configurazione, il 3.5 suona come dovrebbe essere.
Nathan Cooper,

51
Non userei nemmeno una costante per 1024 ^ 2; 1024*1024plz!
Lightness Races con Monica il

44

Ci sono due domande che faccio quando si tratta di numeri magici.

Il numero ha un nome?

I nomi sono utili perché possiamo leggere il nome e capire lo scopo del numero dietro di esso. Le costanti di denominazione possono aumentare la leggibilità se il nome è più semplice da comprendere rispetto al numero che sostituisce e il nome costante è conciso.

Chiaramente, costanti come pi, e, et al. hanno nomi significativi. Un valore come 1024 potrebbe essere BYTES_PER_KBma mi aspetterei anche che qualsiasi sviluppatore saprebbe cosa significa 1024. Il pubblico previsto per il codice sorgente sono programmatori professionisti che dovrebbero avere le basi per conoscere i vari poteri di due e perché sono usati.

Viene utilizzato in più posizioni?

Mentre i nomi sono un punto di forza delle costanti, un altro è la riusabilità. Se è probabile che un valore cambi, può essere modificato in una posizione anziché doverlo cercare in più posizioni.

La tua domanda

Nel caso della tua domanda, userei il numero così com'è.

Nome: esiste un nome per quel numero, ma non è nulla di veramente utile. Non rappresenta una costante matematica o un valore specificato in alcun documento di requisiti.

Posizioni: anche se utilizzata in più posizioni, non cambierà mai, annullando questo vantaggio.


1
La ragione per usare le costanti invece dei numeri magici non è solo perché i numeri cambieranno, ma anche per la leggibilità e l'autocertificazione.
Tulains Córdova,

4
@ user61852: le costanti con nome non sono sempre più leggibili. Lo sono spesso, ma non sempre.
whatsisname

2
Personalmente, uso invece queste due domande: "Questo valore cambierà mai nel corso della vita del programma?" e "Gli sviluppatori che mi aspetto di lavorare su questo software capiranno a cosa serve questo numero?"
Gort the Robot

4
Intendi il problema Y2K? Non sono sicuro che sia rilevante qui. Sì, c'era un sacco di codice come 'date - 1900', ma in quel codice il problema non era il numero magico "1900".
Gort il robot il

1
Questa risposta potrebbe trarre vantaggio da una menzione, secondo cui alcuni numeri "ovvi", 1024 sicuramente uno, sono tali che è molto probabile che altri sviluppatori li scrivano spontaneamente come numeri, anche quando qualcuno definisce una costante nominata per loro. Probabilmente non penserei nemmeno di cercare nel codice sorgente la costante esistente per 1024 se non sapessi già che ce n'è uno, se dovessi usare 1024 nella conversione della quantità di byte.
hyde,

27

Questa citazione

Non è il numero il problema, è la magia.

come detto da Jörg W Mittag risponde abbastanza bene a questa domanda.

Alcuni numeri semplicemente non sono magici in un contesto particolare. Nell'esempio fornito nella domanda, le unità di misura sono state specificate dai nomi delle variabili e l'operazione in corso era abbastanza chiara.

Quindi 1024non è magico perché il contesto rende molto chiaro che è il valore appropriato e costante da usare per le conversioni.

Allo stesso modo, un esempio di:

var numDays = numHours / 24; 

è altrettanto chiaro e non magico perché è risaputo che ci sono 24 ore al giorno.


21
Ma ... ma ... 24 può cambiare! La Terra sta rallentando la sua rotazione e alla fine avrà 25 ore! (Ovviamente saremo tutti morti entro quel momento, facendo manutenzione a quel software il problema di qualcun altro)

14
Cosa succede dopo che il software è stato distribuito su Marte? Dovresti iniettare quella costante ...
durron597

8
@ durron597: cosa succede se il tuo programma funziona abbastanza a lungo da rallentare la Terra in quel periodo ? Non dovresti iniettare una costante, piuttosto una funzione che accetta un timestamp (impostazione predefinita ora) e restituisce il numero di ore del giorno in cui il timestamp cade ;-)
Steve Jessop,

13
Dovrai imparare YAGNI.
whatsisname

3
@ durron597 Non succede nulla di speciale quando il tuo software di cronometraggio viene distribuito su Marte, perché per convenzione i giorni di Marte durano 24 ore, ma ogni ora è del 2,7% in più di quanto non sia sulla Terra . Ovviamente, né un giorno siderale terrestre né un giorno solare terrestre sono esattamente 24 ore (i numeri esatti sono sulla stessa pagina), quindi non puoi 24 comunque usarli ! Come menzionato Izkata, i secondi di salto fanno male. Forse avresti avuto più fortuna usando la costante 24su Marte che sulla Terra!
un CVn il

16

Altri manifesti hanno affermato che la conversione in corso è "ovvia", ma non sono d'accordo. La domanda originale, a questo punto nel tempo, include:

kilobytes kibibytes

Quindi già so che l'autore è o era confuso. La pagina di Wikipedia aggiunge confusione:

1000 = KB kilobyte (metric)
1024 = kB kilobyte (JEDEC)
1024 = KiB kibibyte (IEC)

Quindi "Kilobyte" può essere usato per indicare sia un fattore 1000 che 1024, con l'unica differenza nella stenografia è la capitalizzazione della "k". Inoltre, 1024 può significare kilobyte (JEDEC) o kibibyte (IEC). Perché non distruggere tutta quella confusione con una costante con un nome significativo? A proposito, questo thread ha usato "BYTES_PER_KBYTE" frequentemente e non è meno ambiguo. KBYTE: è KIBIBYTE o KILOBYTE? Preferirei ignorare JEDEC e avere BYTES_PER_KILOBYTE = 1000e BYTES_PER_KIBIBYTE = 1024. Niente più confusione.

Il motivo per cui le persone come me e molti altri là fuori hanno opinioni "militanti" (per citare un commentatore qui) sulla denominazione dei numeri magici è tutto sulla documentazione di ciò che si intende fare e sulla rimozione dell'ambiguità. E in realtà hai scelto un'unità che ha creato molta confusione.

Se vedo:

int BYTES_PER_KIBIBYTE = 1024;  
...  
var kibibytes = bytes / BYTES_PER_KIBIBYTE;  

Quindi è immediatamente ovvio ciò che l'autore intendeva fare e non c'è alcuna ambiguità. Posso controllare la costante in pochi secondi (anche se si trova in un altro file), quindi anche se non è "istantaneo", è abbastanza vicino all'istante.

Alla fine, potrebbe essere ovvio quando lo scrivi, ma sarà meno ovvio quando ci tornerai più tardi, e potrebbe essere ancora meno ovvio quando qualcun altro lo modifica. Ci vogliono 10 secondi per fare una costante; potrebbe volerci mezz'ora o più per eseguire il debug di un problema con le unità (il codice non ti salterà fuori e ti dirà che le unità sono sbagliate, dovrai fare tu stesso la matematica per capirlo, e probabilmente dovrai dare la caccia a 10 strade diverse prima di controllare le unità).


2
Buona risposta al contatore. Sarebbe più forte se prendessi in considerazione la cultura del singolo team. Se hai creduto al mio profilo SE , sono abbastanza grande per precedere quegli standard particolari. Quindi l'unica confusione deriva da "qual è l'attuale termine (non) standard?" E probabilmente saresti al sicuro supponendo che io lavori con una squadra di compagni dinosauri che hanno tutti la stessa terminologia (non) difficoltà.

@ GlenH7: IMHO, la potenza di due unità basate avrebbe dovuto essere conservata per l'archiviazione, poiché è allocata in blocchi di potenza di due dimensioni. Se la dimensione minima di allocazione è 4096 byte, ha più senso disporre di un'unità per la quantità di memoria richiesta per contenere 256 file di dimensioni minime o la quantità di memoria richiesta per contenere 244.140625? Personalmente, vedo la differenza tra i megabyte del produttore del disco rigido e altri megabyte per essere analoga alla differenza tra i pollici diagonali del televisore e i pollici diagonali reali.
supercat

@Ryan: Per questo caso specifico, preferirei essere militante sull'adozione di unità standard: KB è di 1000 byte o il codice è errato e 1024 byte è KiB o il codice è errato. Questo è l'unico modo in cui riusciremo mai a superare il problema "le unità sono ambigue". Diverse persone che definiscono "costanti magiche" (come KB) in modo diverso non aiuteranno.
Brendan,

11

La definizione di un nome come riferimento a un valore numerico suggerisce che ogni volta che è necessario un valore diverso in un posto che utilizza quel nome, sarà probabilmente necessario in tutti. Tende anche a suggerire che cambiare il valore numerico assegnato al nome è un modo legittimo di cambiare il valore. Una tale implicazione può essere utile quando è vera e pericolosa quando è falsa.

Il fatto che due luoghi diversi utilizzino un particolare valore letterale (ad es. 1024) suggerirà debolmente che i cambiamenti che inducono un programmatore a cambiarne uno sono in qualche modo suscettibili di ispirare il programmatore a voler cambiare altri, ma che l'implicazione è molto più debole di quanto si applicherebbe se il programmatore ha assegnato un nome a tale costante.

Un grave pericolo con qualcosa del genere #define BYTES_PER_KBYTE 1024è che potrebbe suggerire a qualcuno che incontra printf("File size is %1.1fkB",size*(1.0/BYTES_PER_KBYTE));che un modo sicuro per far usare il codice a migliaia di byte sarebbe cambiare la #definedichiarazione. Tale modifica potrebbe essere disastrosa, tuttavia, se ad esempio un altro codice non correlato riceve la dimensione di un oggetto in Kbyte e utilizza tale costante quando alloca un buffer per esso.

Potrebbe essere ragionevole usare #define BYTES_PER_KBYTE_FOR_USAGE_REPORT 1024e #define BYTES_PER_KBYTE_REPORTED_BY_FNOBULATOR 1024, assegnare un nome diverso per ogni diverso scopo servito dalla costante 1024, ma ciò comporterebbe la definizione e l'utilizzo di molti identificatori esattamente una volta. Inoltre, in molti casi, è più facile capire cosa significhi un valore se si vede il codice dove viene usato, ed è più facile capire dove significa codice se si vedono i valori di tutte le costanti utilizzate in esso. Se un letterale numerico viene usato una sola volta per uno scopo particolare, scrivere il letterale nel luogo in cui viene usato spesso produrrà un codice più comprensibile che assegnargli un'etichetta in un posto e usarne il valore altrove.


7

Vorrei usare solo il numero, tuttavia penso che non sia stato sollevato un problema importante: lo stesso numero può significare cose diverse in contesti diversi e questo può complicare il refactoring.

1024 è anche il numero di KiB per MiB. Supponiamo di usare 1024 per rappresentare quel calcolo da qualche parte, o in più punti, e ora dobbiamo cambiarlo per calcolare invece GiB. Cambiare la costante è più facile di una ricerca / sostituzione globale in cui è possibile cambiare accidentalmente quella sbagliata in alcuni punti o mancare in altri.

Oppure potrebbe anche essere una piccola maschera introdotta da un programmatore pigro che deve essere aggiornata un giorno.

È un esempio un po 'inventato, ma in alcune basi di codice ciò può causare problemi durante il refactoring o l'aggiornamento per nuovi requisiti. Per questo caso particolare, però, non considero il numero semplice come una forma davvero negativa, specialmente se è possibile racchiudere il calcolo in un metodo per il riutilizzo, probabilmente lo farei da solo, ma considero la costante più "corretta".

Se si utilizzano costanti con nome, tuttavia, come dice Supercat, è importante considerare se anche il contesto è importante e se sono necessari più nomi.

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.