¿Todos los números mágicos son iguales?

77

En un proyecto reciente, necesitaba convertir de bytes a kilobytes de kibibyte . El código fue lo suficientemente sencillo:

var kBval = byteVal / 1024;

Después de escribir eso, conseguí que el resto de la función funcionara y seguí adelante.

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

Entonces, ¿los números que son constantes bien conocidos 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 un "número mágico"? - son similares, pero son preguntas mucho más amplias de lo que estoy preguntando. Mi pregunta se centra en números constantes bien conocidos que no se abordan en esas preguntas.

Eliminando números mágicos: ¿Cuándo es el momento de decir "No"? también está relacionado, pero se centra en la refactorización en lugar de si un número constante es un número mágico o no.

Comunidad
fuente
17
En realidad trabajé en un proyecto en el que habían creado como constantes, FOUR_HUNDRED_FOUR = 404. I trabajado en otro proyecto donde estaban militantes sobre el uso de cadenas constantes en lugar de literales, por lo que tenían decenas de líneas de código que parecían,DATABASE = "database"
Rob
82
Definitivamente use 1024, porque de lo contrario su equipo de desarrollo pasará todo su tiempo discutiendo sobre si es "kilobytes" o "kibibytes".
Gort the Robot
66
Puede considerar que 1024 es kibi y #define KIBIcomo 1024, MEBIcomo 1024 * 1024 ...
ysdx
66
@Rob Y: suena como buenos viejos programadores de Fortran. Porque ese lenguaje de programación obligó a los programadores a hacerlo. Sí, allí verá constantes como ZERO=0, ONE=1, TWO=2y cuando los programas son portados a otras lenguas (o los programadores no cambiar el comportamiento cuando se cambia su lenguaje) verás que allí también y hay que rezar que jamás alguien a cambiar a ONE=2...
Holger
44
@NoctisSkytower Mi equipo prefiere usar declaraciones de división explícitas en lugar de operadores de desplazamiento de bits debido a los múltiples idiomas que usamos y la implementación potencialmente inconsistente en esos idiomas. Del mismo modo, los valores negativos se manejan de manera inconsistente con desplazamiento bit a bit. Si bien no necesariamente tenemos valores de bytes negativos, ciertamente tenemos valores negativos con otras unidades de medida que convertimos.

Respuestas:

103

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

Creo que en ese caso, 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 de inmediato por qué el valor es 1024. Por otro lado, si estuviera convirtiendo bytes a megabytes, definiría la constante BYTES_PER_MBYTE o similar porque la constante 1.048.576 no es tan obvia que es 1024 ^ 2, o que es incluso correcto

Lo mismo ocurre con los valores dictados por requisitos o estándares, que solo se utilizan en un lugar. Encuentro que poner la constante en su lugar 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

Me parece mejor que

SomeTest = DataSample < SOME_THRESHOLD_VALUE

Solo cuando SOME_THRESHOLD_VALUEse usa en varios lugares, la compensación se convierte en algo valioso para definir una constante, en mi opinión.

whatsisname
fuente
67
"El problema con los números mágicos es cuando son mágicos" - ¡Esta es una explicación tan brillante de ese concepto! ¡Estoy siendo serio! +1 solo por esa oración.
Jörg W Mittag
20
Aquí hay uno que se me ocurrió: "no es el número el problema, es la magia".
Jörg W Mittag
10
¿1024 es obvio para quién? ¿No es esa la justificación para cada número mágico? Todos los números mágicos se usan porque son obvios para quien los escribió. ¿No es 9.8 también obvio? Para mí es bastante obvio que es la aceleración de la gravedad en la Tierra, pero crearía una constante, porque lo que es obvio para mí puede no ser obvio para otra persona.
Tulains Córdova
15
No. Un comentario como el de tu "mejor" ejemplo es una gran bandera roja. Es un código que ni siquiera pasa la prueba de legibilidad de la persona que lo escribe en ese momento. Daré un ejemplo. e^i*pi = -1es mucho más explícito (mejor) que 2.718^i*3.142 = -1. Las variables importan y no son solo para el código común. El código está escrito para leer primero, compilando segundo. Además, las especificaciones cambian (mucho). Mientras que el 1024 probablemente no debería estar en configuración, el 3.5 suena como debería estar.
Nathan Cooper
51
Tampoco usaría una constante para 1024 ^ 2; 1024*1024por favor!
Carreras de ligereza en órbita
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 comprender el propósito del número detrás de él. Nombrar constantes puede aumentar la legibilidad si el nombre es más fácil de entender que el número que reemplaza y el nombre constante es conciso.

Claramente, constantes como pi, e, et al. tener nombres significativos Podría ser un valor como 1024 BYTES_PER_KBpero también esperaría que cualquier desarrollador supiera 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 usa en múltiples ubicaciones?

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

Tu pregunta

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

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

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


fuente
1
La razón para usar constantes en lugar de números mágicos no es solo porque dichos números cambiarán, sino también por la legibilidad y la autodocumentación.
Tulains Córdova
44
@ user61852: las constantes con nombre no siempre son más legibles. A menudo lo son, pero no siempre.
whatsisname
2
Personalmente, uso estas dos preguntas en su lugar: "¿Cambiará este valor alguna vez en la vida del programa?" y "¿Los desarrolladores que espero que estén trabajando en este software entenderán para qué sirve este número?"
Gort the Robot
44
¿Te refieres al problema de Y2K? No estoy seguro de que sea relevante aquí. Sí, había muchos códigos como 'fecha - 1900', pero en ese código, el problema no era el número mágico "1900".
Gort the Robot
1
Esta respuesta podría beneficiarse de una mención, que algunos números "obvios", 1024 definitivamente uno, son tales que es muy probable que otros desarrolladores los escriban espontáneamente como números, incluso cuando alguien define una constante con nombre para ellos. Por mi parte, lo más probable es que ni siquiera piense en buscar en el código fuente la constante existente para 1024 si no supiera que hay una, si necesito usar 1024 en la conversión de la cantidad de bytes.
hyde
27

Esta cita

El problema no es el número, es la magia.

como se ha dicho 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 era bastante clara.

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

Asimismo, 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.

Comunidad
fuente
21
Pero ... pero ... ¡24 puede cambiar! ¡La Tierra está disminuyendo su rotación y eventualmente tendrá 25 horas! (Por supuesto, todos vamos a estar muerto para entonces, haciendo que el mantenimiento de dicho software el problema de otro)
14
¿Qué sucede después de que su software se implementa en Marte? Deberías estar inyectando esa constante ...
durron597
8
@ durron597: ¿y si su programa se ejecuta el tiempo suficiente para que la tierra se desacelere durante ese tiempo ? No debería estar inyectando una constante, más bien una función que acepta una marca de tiempo (predeterminada ahora) y devuelve el número de horas en el día que cae la marca de tiempo ;-)
Steve Jessop
13
Necesitarás aprender YAGNI.
whatsisname
3
@ durron597 No sucede nada especial cuando su software de cronometraje se implementa en Marte, porque por convención los días de Marte duran 24 horas, pero cada hora es un 2,7% más larga que en la Tierra . Por supuesto, ni un día sideral de la Tierra ni un día solar de la Tierra son exactamente 24 horas (los números exactos están en esa misma página), ¡así que no puedes usarlos de 24 todos modos! Como mencionó Izkata, los segundos de salto duelen. ¡Tal vez tendrías mejor suerte usando la constante 24en Marte que en la Tierra!
un CVn
16

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

kilobytes kibibytes

Entonces 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" puede usarse para significar tanto un factor de 1000 como 1024, con la única diferencia en taquigrafía que es la capitalización de la 'k'. Además de eso, 1024 puede significar kilobyte (JEDEC) o kibibyte (IEC). ¿Por qué no destruir toda esa confusión directamente 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 a JEDEC y tener BYTES_PER_KILOBYTE = 1000y BYTES_PER_KIBIBYTE = 1024. No más confusión.

La razón por la cual la gente como yo, y muchos otros por ahí, tienen opiniones 'militantes' (para citar a un comentarista aquí) sobre nombrar números mágicos se trata de documentar lo que pretendes hacer y eliminar la ambigüedad. Y en realidad elegiste una unidad que ha generado mucha confusión.

Si yo 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 es 'instantáneo', está lo suficientemente cerca de instantáneo.

Al final, puede ser obvio cuando lo estás escribiendo, pero será menos obvio cuando vuelvas a hacerlo más tarde, y puede ser aún menos obvio cuando alguien más lo edite. Se necesitan 10 segundos para hacer una constante; Podría tomar media hora o más para depurar un problema con las unidades (el código no te saltará a la vista y te dirá que las unidades están equivocadas, tendrás que hacer los cálculos tú mismo para resolverlo, y probablemente cazarás 10 avenidas diferentes antes de revisar las unidades).

Shaz
fuente
2
Buena contra respuesta. Sería más fuerte si tuviera en cuenta la cultura individual del equipo. Si creías en mi perfil SE , soy lo suficientemente mayor como para preceder a esos estándares particulares. Entonces, la única confusión proviene de "¿cuál es el término estándar (no) actual?" Y es probable que esté seguro al suponer que trabajo con un equipo de dinosaurios que tienen la misma terminología (no) dificultades.
@ GlenH7: En mi humilde opinión, las unidades basadas en la potencia de dos deberían haberse almacenado, ya que se asignan en trozos de dos dimensiones. Si el tamaño mínimo de asignación es de 4096 bytes, ¿tiene más sentido tener una unidad para la cantidad de almacenamiento requerida para contener 256 archivos de tamaño mínimo, o la cantidad de almacenamiento requerida para contener 244.140625 de dichos archivos? Personalmente, considero que la diferencia entre los megabytes del fabricante del disco duro y otros megabytes es análoga a la diferencia entre las pulgadas diagonales del televisor y las pulgadas diagonales reales.
supercat
@ Ryan: Para este caso específico, prefiero ser militante sobre la adopción de unidades estándar: KB es de 1000 bytes o el código es incorrecto, y 1024 bytes es KiB o el código es incorrecto. Esta es la única forma en que vamos a superar el problema de "las unidades son ambiguas". Diferentes personas que definen "constantes mágicas" (como KB) de manera diferente no ayudarán.
Brendan
11

La definición de 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, probablemente 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. Tal 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 asignó un nombre a tal constante.

Un peligro importante con algo así #define BYTES_PER_KBYTE 1024es que podría sugerirle a alguien que encuentra 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 #definedeclaración. Sin embargo, dicho 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 al asignarle un búfer.

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

Super gato
fuente
7

Me inclinaría por usar solo el número, sin embargo, creo que no se ha planteado una cuestión importante: el mismo número puede significar cosas diferentes en diferentes contextos, 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 varios lugares, y ahora necesitamos cambiarlo para calcular GiB en su lugar. 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 necesita actualizarse algún día.

Es un ejemplo un poco artificial, pero en algunas bases de código esto puede causar problemas al refactorizar o actualizar los 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 su reutilización, probablemente lo haría yo mismo, pero consideraría la constante más 'correcta'.

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

Nick P
fuente