Tutti i numeri magici sono creati uguali?

77

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

var kBval = byteVal / 1024;

Dopo averlo scritto, ho ottenuto il resto della funzione working & spostato.

Ma più tardi, ho iniziato a chiedermi se avessi incorporato un numero magico all'interno del mio codice . Una parte di me dice che andava bene perché il numero è una costante fissa e dovrebbe essere prontamente compreso. Ma un'altra parte di me pensa che sarebbe stato super-chiaro se fosse avvolto 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 "magico" numero "? - sono simili, ma sono domande molto più ampie di quello che sto chiedendo. La mia domanda si concentra su numeri costanti ben noti che non sono affrontati in quelle domande.

Eliminazione dei numeri magici: quando è il tempo per dire "No"? è anche correlato, ma è focalizzato sul refactoring, a differenza del fatto che un numero costante sia o meno un numero magico.

    
posta GlenH7 17.12.2014 - 20:14
fonte

6 risposte

103

Non tutti i numeri magici sono uguali.

Penso che in quell'istante, 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 si convertono i byte in megabyte, definirei la costante BYTES_PER_MBYTE o simile perché la costante 1.048.576 non è così ovvia che la sua 1024 ^ 2, o che sia anche corretto.

Lo stesso vale per i valori dettati da requisiti o standard, che vengono utilizzati solo in un unico posto. Trovo che mettere la costante al suo posto con un commento alla fonte pertinente sia più facile da gestire 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_VALUE è usato in più posti, a mio parere, il tradeoff ne vale la pena per definire una costante.

    
risposta data 17.12.2014 - 20:29
fonte
44

Ci sono due domande che chiedo 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ù facile da capire rispetto al numero che sostituisce e il nome della costante è conciso.

Chiaramente, costanti come pi, e, et al. avere nomi significativi Un valore come 1024 potrebbe essere BYTES_PER_KB , ma mi aspetto anche che ogni sviluppatore sappia cosa significa 1024. Il pubblico previsto per il codice sorgente è programmatori professionisti che dovrebbero avere lo sfondo per conoscere i vari poteri di due e perché vengono utilizzati.

Viene utilizzato in più postazioni?

Mentre i nomi sono una forza delle costanti, un altro è la riusabilità. Se è probabile che un valore cambi, è possibile modificarlo in un unico posto invece di doverlo cercare in più posizioni.

La tua domanda

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

Nome: c'è un nome per quel numero, ma non è niente di veramente utile. Non rappresenta una costante o valore matematico specificato in alcun documento dei requisiti.

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

    
risposta data 17.12.2014 - 20:43
fonte
27

Questa citazione

It's not the number that's the problem, it's the magic.

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 1024 non è magico perché il contesto rende molto chiaro che è il valore costante appropriato da utilizzare per le conversioni.

Allo stesso modo, un esempio di:

var numDays = numHours / 24; 

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

    
risposta data 17.12.2014 - 20:51
fonte
16

Altri manifesti hanno affermato che la conversione è "ovvia", ma non sono d'accordo. La domanda originale, al momento, include:

kilobytes kibibytes

Quindi so già 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 di 1000 che di 1024, con l'unica differenza nella stenografia essendo la maiuscola della "k". Inoltre, 1024 può significare kilobyte (JEDEC) o kibibyte (IEC). Perché non frantumare tutta questa confusione con una costante con un nome significativo? A proposito, questo thread ha usato "BYTES_PER_KBYTE" frequentemente, e questo non è meno ambiguo. KBYTE: è KIBIBYTE o KILOBYTE? Preferirei ignorare JEDEC e avere BYTES_PER_KILOBYTE = 1000 e 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) su come nominare numeri magici è tutto sulla documentazione di ciò che intendi fare, e rimuovendo l'ambiguità. E hai effettivamente scelto un'unità che ha portato a molta confusione.

Se vedo:

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

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

Alla fine, potrebbe essere ovvio quando lo stai scrivendo, ma sarà meno ovvio quando torni più tardi, e potrebbe essere ancora meno ovvio quando qualcuno lo modifica. Ci vogliono 10 secondi per fare una costante; potrebbe impiegare mezz'ora o più per eseguire il debug di un problema con le unità (il codice non ti salterà addosso e ti dirà che le unità sono sbagliate, dovrai fare i calcoli tu stesso per capirlo, e probabilmente scoverai 10 strade diverse prima di controllare le unità).

    
risposta data 18.12.2014 - 23:34
fonte
11

La definizione di un nome come riferimento a un valore numerico suggerisce che ogni volta che è necessario un valore diverso in un punto che utilizza quel nome, sarà probabilmente necessario in tutti. Inoltre, tende a suggerire che la modifica del valore numerico assegnato al nome è un modo legittimo di modificare il valore. 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 potrebbero spingere un programmatore a cambiarne uno in qualche modo spingono il programmatore a voler cambiare gli altri, ma questa implicazione è molto più debole di quanto si applicherebbe se il programmatore assegnasse un nome a tale costante.

Un grave pericolo con qualcosa come #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 sì che il codice usi migliaia di byte sarebbe quello di cambiare l'istruzione #define . Tale cambiamento potrebbe essere disastroso, tuttavia, se ad es. qualche altro codice non collegato riceve la dimensione di un oggetto in Kbyte e usa quella costante quando assegna un buffer per questo.

Potrebbe essere ragionevole usare #define BYTES_PER_KBYTE_FOR_USAGE_REPORT 1024 e #define BYTES_PER_KBYTE_REPORTED_BY_FNOBULATOR 1024 , assegnando un nome diverso per ogni diverso scopo servito dalla costante 1024, ma ciò comporterebbe l'identificazione e l'utilizzo di molti identificatori una sola volta. Inoltre, in molti casi, è più facile capire cosa significhi un valore se si vede il codice dove è usato, ed è più facile capire dove si intende il codice se si vedono i valori di tutte le costanti utilizzate al suo interno. Se un valore letterale numerico viene utilizzato una sola volta per uno scopo particolare, scrivere il letterale nel punto in cui viene utilizzato genererà spesso un codice più comprensibile rispetto all'assegnazione di un'etichetta ad esso in un unico punto e utilizzando il suo valore da qualche altra parte.

    
risposta data 17.12.2014 - 22:05
fonte
7

Mi piacerebbe usare solo il numero, tuttavia penso che un problema importante non sia stato sollevato: 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 questo calcolo da qualche parte, o in più punti, e ora dobbiamo cambiarlo per calcolare invece GiB. La modifica della costante è più semplice di una ricerca / sostituzione globale in cui potresti cambiare accidentalmente la parte sbagliata in alcuni punti o mancarla in altri.

Oppure potrebbe anche essere una maschera di bit introdotta da un programmatore pigro che deve essere aggiornato un giorno.

È un esempio un po 'forzato, ma in alcune basi di codice questo può causare problemi durante il refactoring o l'aggiornamento di nuovi requisiti. In questo caso specifico, però, non considererei il numero normale come una pessima forma, specialmente se riesci a racchiudere il calcolo in un metodo da riutilizzare, probabilmente lo farei io stesso, ma consideri la costante più "corretta".

Se usi le costanti denominate, tuttavia, siccome il supercat dice che è importante considerare se anche il contesto conta, e se hai bisogno di più nomi.

    
risposta data 18.12.2014 - 01:46
fonte

Leggi altre domande sui tag