¿Son todos los números mágicos creados iguales?

77

En un proyecto reciente, necesitaba convertir de bytes a kilobytes kibibyte . El código era bastante sencillo:

var kBval = byteVal / 1024;

Después de escribir eso, obtuve el resto de la función trabajando & siguió adelante.

Pero más tarde, comencé a preguntarme si acababa de insertar un número mágico dentro de mi código . Una parte de mí dice que estaba bien porque el número es una constante fija y debería entenderse fácilmente. Pero otra parte de mí piensa que habría sido súper claro si se envolviera en una constante definida como BYTES_PER_KBYTE .

¿Entonces los números que son constantes bien conocidas son realmente mágicos o no?

Preguntas relacionadas:

¿Cuándo es un número un número mágico? y ¿Cada número en el código se considera una "magia"? número "? - son similares, pero son preguntas mucho más amplias de lo que estoy preguntando. Mi pregunta se centra en números constantes conocidos que no se abordan en esas preguntas.

Eliminando los números mágicos: ¿Cuándo es ¿El tiempo para decir "No"? también está relacionado, pero se enfoca en refactorizar en oposición a si un número constante es un número mágico o no.

    
pregunta GlenH7 17.12.2014 - 20:14

6 respuestas

103

No todos los números mágicos son iguales.

Creo que en esa instancia, esa constante está bien. El problema con los números mágicos es cuando son mágicos, es decir, no está claro cuál es su origen, por qué el valor es lo que es, o si el valor es correcto o no.

Ocultar 1024 detrás de BYTES_PER_KBYTE también significa que no ves al instante si es correcto o no.

Esperaría que alguien supiera inmediatamente por qué el valor es 1024. Por otro lado, si estuvieras convirtiendo bytes a megabytes, definiría la constante BYTES_PER_MBYTE o similar porque la constante 1,048,576 no es tan obvia que su 1024 ^ 2, o que incluso es correcto.

Lo mismo ocurre con los valores dictados por requisitos o estándares, que solo se utilizan en un lugar. Me parece que poner la constante en el lugar correcto con un comentario a la fuente relevante es más fácil de tratar que definirlo en otro lugar y tener que perseguir ambas partes, por ejemplo:

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

Encuentro mejor que

SomeTest = DataSample < SOME_THRESHOLD_VALUE

En mi opinión, solo cuando se usa SOME_THRESHOLD_VALUE en varios lugares, la compensación vale la pena para definir una constante.

    
respondido por el whatsisname 17.12.2014 - 20:29
44

Hay dos preguntas que hago cuando se trata de números mágicos.

¿El número tiene un nombre?

Los nombres son útiles porque podemos leer el nombre y entender el propósito del número detrás de él. Las constantes de nombres pueden aumentar la legibilidad si el nombre es más fácil de entender que el número al que reemplaza y el nombre de la constante es conciso.

Claramente, constantes como pi, e, et al. tienen nombres significativos Un valor como 1024 podría ser BYTES_PER_KB pero también espero que cualquier desarrollador sepa lo que significa 1024. La audiencia prevista para el código fuente son los programadores profesionales que deben tener los antecedentes para conocer los diversos poderes de dos y por qué se utilizan.

¿Se utiliza en varias ubicaciones?

Mientras que los nombres son una fortaleza de las constantes, otra es la reutilización. Si es probable que un valor cambie, se puede cambiar en un solo lugar en lugar de tener que buscarlo en varias ubicaciones.

Tu pregunta

En el caso de su pregunta, usaría el número como está.

Nombre: hay un nombre para ese número, pero no es nada realmente útil. No representa una constante matemática ni un valor especificado en ningún documento de requisitos.

Ubicaciones: incluso si se usa en múltiples ubicaciones, nunca cambiará, anulando este beneficio.

    
respondido por el user22815 17.12.2014 - 20:43
27

Esta cita

  

No es el número el problema, es la magia.

como se dijo por Jörg W Mittag responde a esta pregunta bastante bien.

Algunos números simplemente no son mágicos dentro de un contexto particular. En el ejemplo proporcionado en la pregunta, las unidades de medida fueron especificadas por los nombres de las variables y la operación que estaba teniendo lugar fue bastante clara.

Entonces, 1024 no es mágico porque el contexto deja muy claro que es el valor constante y apropiado para usar en las conversiones.

Igualmente, un ejemplo de:

var numDays = numHours / 24; 

es igualmente claro y no mágico porque es bien sabido que hay 24 horas en el día.

    
respondido por el GlenH7 17.12.2014 - 20:51
16

Otros carteles han mencionado que la conversión que se está produciendo es 'obvia', pero no estoy de acuerdo. La pregunta original, en este punto en el tiempo, incluye:

  

kilobytes kibibytes

Ya sé que el autor está o estaba confundido. La página de Wikipedia se suma a la confusión:

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

Por lo tanto, "Kilobyte" se puede usar para significar un factor de 1000 y 1024, con la única diferencia en taquigrafía es la capitalización de la 'k'. Además de eso, 1024 puede significar kilobyte (JEDEC) o kibibyte (IEC). ¿Por qué no destruir completamente toda esa confusión con una constante con un nombre significativo? Por cierto, este hilo ha usado "BYTES_PER_KBYTE" con frecuencia, y eso no es menos ambiguo. KBYTE: es KIBIBYTE o KILOBYTE? Prefiero ignorar JEDEC y tener BYTES_PER_KILOBYTE = 1000 y BYTES_PER_KIBIBYTE = 1024 . No más confusión.

La razón por la que la gente como yo, y muchos otros, tienen opiniones "militantes" (para citar a un comentarista aquí) sobre cómo nombrar números mágicos se trata de documentar lo que pretendes hacer, y eliminando la ambigüedad. Y realmente escogiste una unidad que ha llevado a mucha confusión.

Si veo:

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

Entonces es inmediatamente obvio lo que el autor pretendía hacer, y no hay ambigüedad. Puedo verificar la constante en cuestión de segundos (incluso si está en otro archivo), por lo que aunque no sea "instantánea", es lo suficientemente cercana a la instantánea.

Al final, puede ser obvio cuando lo escribes, pero será menos obvio cuando vuelvas a leerlo más tarde, y puede ser incluso menos obvio cuando alguien más lo edite. Se tarda 10 segundos en hacer una constante; podría tardar media hora o más en solucionar un problema con las unidades (el código no va a saltarte y te dirá que las unidades están mal, tendrás que hacer los cálculos para resolverlo, y es probable que encuentres 10 avenidas diferentes antes de verificar las unidades).

    
respondido por el Shaz 18.12.2014 - 23:34
11

Definir un nombre como referencia a un valor numérico sugiere que siempre que se necesite un valor diferente en un lugar que use ese nombre, es probable que sea necesario en todos. También tiende a sugerir que cambiar el valor numérico asignado al nombre es una forma legítima de cambiar el valor. Dicha implicación puede ser útil cuando es verdadera y peligrosa cuando es falsa.

El hecho de que dos lugares diferentes utilicen un valor literal particular (por ejemplo, 1024) sugerirá débilmente que los cambios que incitarían a un programador a cambiar uno probablemente inspirarán al programador a querer cambiar a otros, pero esa implicación es mucho más débil de lo que se aplicaría si el programador asignara un nombre a dicha constante.

Un peligro importante con algo como #define BYTES_PER_KBYTE 1024 es que podría sugerir a alguien que se encuentre con printf("File size is %1.1fkB",size*(1.0/BYTES_PER_KBYTE)); que una forma segura de hacer que el código use miles de bytes sería cambiar la declaración #define . Sin embargo, tal cambio podría ser desastroso si, por ejemplo, algún otro código no relacionado recibe el tamaño de un objeto en Kbytes y usa esa constante cuando se le asigna un búfer.

Puede ser razonable usar #define BYTES_PER_KBYTE_FOR_USAGE_REPORT 1024 y #define BYTES_PER_KBYTE_REPORTED_BY_FNOBULATOR 1024 , asignando un nombre diferente para cada propósito diferente servido por la constante 1024, pero eso daría lugar a que muchos identificadores se definan y se usen exactamente una vez. Además, en muchos casos, es más fácil entender lo que significa un valor si uno ve el código donde se usa, y es más fácil averiguar dónde significa código si uno ve los valores de las constantes que se usan en él. Si un literal numérico solo se usa una vez para un propósito en particular, escribir el literal en el lugar donde se usa a menudo generará un código más comprensible que asignarle una etiqueta en un lugar y usar su valor en otro lugar.

    
respondido por el supercat 17.12.2014 - 22:05
7

Me inclino por usar solo el número, sin embargo, creo que no se ha mencionado un problema importante: el mismo número puede significar cosas diferentes en contextos diferentes, y esto puede complicar la refactorización.

1024 es también el número de KiB por MiB. Supongamos que usamos 1024 para representar también ese cálculo en algún lugar, o en múltiples lugares, y ahora tenemos que cambiarlo para calcular GiB. Cambiar la constante es más fácil que una búsqueda / reemplazo global, donde puede cambiar accidentalmente la incorrecta en algunos lugares, o perderla en otros.

O incluso podría ser una máscara de bits introducida por un programador perezoso que debe actualizarse algún día.

Es un poco de un ejemplo artificial, pero en algunas bases de código, esto puede causar problemas al refactorizar o actualizar nuevos requisitos. Sin embargo, para este caso en particular, no consideraría que el número simple sea una forma realmente mala, especialmente si puede incluir el cálculo en un método para reutilizarlo, probablemente lo haría yo mismo pero consideraría que la constante es más "correcta".

Sin embargo, si usa constantes con nombre, como dice supercat, es importante considerar si el contexto también importa, y si necesita varios nombres.

    
respondido por el Nick P 18.12.2014 - 01:46

Lea otras preguntas en las etiquetas