¿Cada número en el código se considera un "número mágico"?

21

Entonces, ¿cada número en el código que estamos enviando a un método como argumento se considera un Número Mágico? Para mí, no debería. Creo que si hay algún número, digamos que es para la longitud mínima del nombre de usuario y comenzamos a usar "6" en el código ... entonces sí, tenemos un problema de mantenimiento y aquí "6" es un número mágico ... pero si estamos llamando a un método en el que uno de sus argumentos acepta un número entero, por ejemplo, como el miembro i-ésimo de una colección y luego pasamos "0" a esa llamada al método, en este caso no veo que "0" sea mágico número. ¿Qué piensas?

Blake
fuente
44
En tu ejemplo, ¿qué representa el 0?
Aaron Kurtzhals
2
En el caso que ilustras, ese "0" no tiene propiedades mágicas de ningún tipo.
Tulains Córdova
44
Todo excepto 0,1 y 42 es mágico
Mawg

Respuestas:

43

Si el significado del número es muy claro en el contexto, no creo que sea un problema de "número mágico".

Ejemplo: supongamos que está tratando de obtener la subcadena de una cadena, desde el principio hasta un token y el código se ve así (lenguaje y biblioteca imaginarios):

s := substring(big_string, 0, findFirstOccurence(SOME_TOKEN, big_string));

En este contexto, el significado del número 0 es lo suficientemente claro. Supongo que podría definirlo START_OF_SUBSTRINGy establecerlo en 0, pero en este caso creo que sería excesivo (aunque sería el enfoque correcto si supiera que el inicio de su subcadena podría no ser 0, pero eso depende de los detalles específicos de tu situación).

Otro ejemplo podría ser si está tratando de determinar si un número es par o impar. Escritura:

isEven := x % 2;

no es tan extraño como:

TWO := 2;
isEven := x % TWO;

Prueba de números negativos como

MINUS_ONE := -1;
isNegativeInt := i <= MINUS_ONE;

también me parece raro, preferiría ver

isNegativeInt := i <= -1;
FrustratedWithFormsDesigner
fuente
66
Para lanzar otro ejemplo allí, en el código donde explícitamente trabajas con grados en un círculo, sería justo usar un número como 360para marcar una rotación completa con el entendimiento de que la mayoría de la gente sabrá lo que eso significa (aunque esto es un caso en el que no habría daño para proporcionar una constante)
KChaloux
11
KChaloux: Si pudiera, haría -1 tu comentario. 360 es un número mágico. Si 360 resulta ser un valor para otra constante, entonces tiene 2 conjuntos para 360 que no están relacionados y son indistinguibles. Junior aparece, vaya "Ese es un número mágico", busque globalmente y reemplace 360 ​​con "Degrees_in_Circle", ejecute todas las pruebas de unidad y regresión, todo pase - entrega la corrección del código. Codifique ahora un desayuno de perros, y todos sabemos lo que sucede después de un corto tiempo .......
mattnz
44
@mattnz: Esperemos que ese tipo de cambio de código a gran escala se detecte rápidamente (con suerte durante la revisión del código, si son tan jóvenes) mucho antes de que entre en producción. Creo que alguien que haría eso en ese contexto probablemente también lo reemplazaría 0en el contexto del ejemplo de mi subcadena. En cuyo caso, esta podría ser la menor cantidad de daño que pueden causar. Ha pasado mucho tiempo desde que hice cualquier codificación que hiciera cálculos geométricos, pero en general, los valores 15, 30, 45, 60, 90, 180, 360 fueron constantes que fueron aceptadas. Nunca he visto a nadie definir FIFTEEN_DEGREES, ...
FrustratedWithFormsDesigner
55
@KChaloux El ejemplo en realidad puede desmoronarse si hay un cambio de grados a radianes. Para 360, estás expresando 1 rotación completa. Como hay varias representaciones para el mismo valor, debe extraerse. Especialmente considerando que 360PI podría tener el mismo aspecto que 2PI (180 rotaciones pero todavía apuntando en la misma dirección al final), o 360 rotaciones lo mismo que 1 rotación, pero los efectos secundarios pueden ser diferentes.
Chris
14
Un poco como un hombre de paja, TWO y MINUS_ONE son absolutamente malos porque reemplazar un número mágico con su representación en texto es, por supuesto, idiota. El nombre de la constante tiene que transmitir su significado. Excepto que sus ejemplos son sobre hechos fundamentales sobre números, estrechamente vinculados a esos números específicos, por lo que no hay realmente ningún significado más allá de eso.
Michael Borgwardt
17
bool hasApples = apples > 0;

Es obvio que cero significa ausencia. Encuentro 0 más fácil de entender que una variable llamada "ausenciaValor".


for(int i=0; i < arr.length; i++)

Es obvio que 0 es la posición inicial. Me confundiría con una variable llamada "firstPosition". Tal variable me haría preguntarme si la posición inicial podría cambiar.

mike30
fuente
14

Sugeriría tres factores clave para decidir si algo debería ser una declaración constante:

  1. ¿Es el número algo que es precisa y concisamente representable?
  2. ¿Hay escenarios plausibles en los que el valor tendría que cambiar, pero el código no tendría que reescribirse?
  3. ¿Alguien que ve el número podría reconocerlo más rápido o menos rápido que alguien que ve una constante nombrada?

Algo como pi probablemente debería escribirse como una constante con nombre, en lugar de como un literal numérico, ya que un literal numérico puede ser innecesariamente detallado, innecesariamente impreciso o ambos. Algo así como el número de ranuras en un caché probablemente debería ser una constante con nombre (aunque vea la nota a continuación) para permitir la posibilidad de expandir el caché sin tener que modificar todo el código que lo usa. Cosas como los números "4", "28" y "29" en el enunciado if ((year % 4)==0) FebruaryDays = 29; else FebruaryDays = 28;probablemente no deberían denominarse constantes, ya que la expresión es casi ciertamente más legible que if ((year % YearsBetweenLeapYears)==0) FebruaryDays = FebruaryDaysInLeapYear; else FebruaryDays = FebruaryDaysInNonLeapYear;. Tenga en cuenta que los mantenedores de las normas han indicado que la duración de febrero de 2100 en ese año no coincidirá con la fórmula anterior, impedimento para manejar correctamente tales fechas (es decir, el código no se disparará por desbordamiento de enteros u otros problemas similares).

Una advertencia importante con la regla # 2 es que, en algunos casos, el código puede depender de números codificados de una manera que no puede representarse fácilmente por una constante con nombre. Por ejemplo, un método que calcula un producto cruzado de dos vectores pasados ​​como parámetros discretos solo será significativo cuando se use en vectores tridimensionales. El número requerido de dimensiones no es un valor que pueda cambiarse significativamente sin reescribir completamente la rutina. Incluso si se preveía una posible necesidad de calcular el producto cruzado de tres vectores de 4 dimensiones, usar una constante con nombre para el valor "3" haría poco para facilitar la satisfacción de esa necesidad.

Super gato
fuente
4

Esto, como todos los principios, es una cuestión de grado. En términos generales, los literales de números en el código fuente son más sospechosos cuanto más grandes son. Una longitud máxima como 10 o una dirección de memoria como 0x587FB0 es obviamente una mala práctica: es casi seguro que tarde o temprano tendrá que repetir estos valores más de una vez, creando un riesgo de incompatibilidad y errores sutiles introducidos en lugares que no fueron cambiado

0 está en el otro extremo de la escala; todavía es sospechoso pero no tanto. ¿Estás usando 0 como valor centinela? Entonces probablemente deberías usar una constante simbólica, solo porque la constante puede explicar lo que significa. ¿Es un acuerdo cultural extremadamente arraigado como "0 significa finalización exitosa"? Eso probablemente está bien. ¿Significa "el primer elemento de una colección"? Eso puede ser inofensivo, pero si hay un método alternativo como el first()que probablemente prefiera.

Kilian Foth
fuente
1
"¿Estás usando 0 como valor centinela?" <- ¿Puedes explicar qué quieres decir con "centinela" aquí? No puedo encontrar una definición que parezca coincidir.
rory.ap
3

Cada número sin nombre que no es inmediatamente obvio por el contexto es un número mágico. Es un poco tonto definir números que tienen un significado que es inmediatamente obvio por el contexto.

En django (python web framework), puedo definir algún campo de base de datos con un número sin formato como:

firstname = models.CharField(max_length=40)
middlename = models.CharField(max_length=40)
lastname =  models.CharField(max_length=40) 

que es más claro (y la práctica recomendada ) que decir

MAX_LENGTH_NAME = 40
...
firstname = models.CharField(max_length=MAX_LENGTH_NAME)
middlename = models.CharField(max_length=MAX_LENGTH_NAME)
lastname =  models.CharField(max_length=MAX_LENGTH_NAME) 

como es poco probable que necesite cambiar la longitud (y siempre se puede comparar con la max_lengthdel campo). Si necesito cambiar la longitud del campo después de implementar inicialmente la aplicación, necesito cambiarlo exactamente en una ubicación por campo en mi código django, y luego escribir una migración para cambiar el esquema de la base de datos. Si alguna vez necesito hacer referencia max_lengtha un campo definido de un tipo de objeto, puedo hacerlo directamente; si esos campos definían una Personclase, puedo usar Person._meta.get_field('firstname').max_lengthpara obtener elmax_lengthsiendo utilizado (que se define en un lugar). El hecho de que se usó el mismo 40 para múltiples campos es irrelevante, ya que es posible que desee cambiarlos de forma independiente. La longitud del nombre nunca debe depender de la longitud del segundo nombre o apellido; son valores separados y pueden cambiar de forma independiente.

A menudo, los índices de matriz pueden usar números sin nombre; como si tuviera un archivo CSV de datos que quiero poner en un diccionario de Python, con el primer elemento en la fila como el diccionario keyque escribiría:

mydict = {}
for row in csv.reader(f):
    mydict[row[0]] = row[1:]

Claro que podría nombrar index_column = 0y hacer algo como:

index_col = 0
mydict = {}
for row in csv.reader(f):
    mydict[row[index_col]] = row[:index_col] + row[index_col+1:]

o peor, definir after_index_col = index_col + 1para deshacerse de él index_col+1, pero eso no hace que el código sea más claro en mi opinión. Además, si le doy index_colun nombre, mejor hago que el código funcione incluso si la columna no es 0 (de ahí la row[:index_col] +parte).

dr jimbob
fuente
77
En realidad, max_lngth=40vs. max_length=MAX_LENGTH_NAMEes un ejemplo clásico de un número mágico que grita como símbolo. Llegará el día en que desee admitir 45 nombres de caracteres, y ahora cada uso de "40" es sospechoso y debe ser examinado cuidadosamente.
Ross Patterson el
1
@RossPatterson: no se trata de C, donde constantemente comparamos con un var MAX_ARRAY_SIZE global, sino un marco web decente. El único lugar donde aparece el número mágico es donde declaras el modelo de la base de datos; todo lo demás se compara con este valor (por ejemplo, 40 no aparece en ninguna otra parte del código). También tenga en cuenta que no puede cambiar esta variable fácilmente sin realizar migraciones de esquema, ya que está vinculada a una base de datos. Si quería cambiar decir 1 carácter segundos nombres de su evidente de inmediato el único lugar a un cambio en el código 40a 1. Tienes que pensar en el contexto.
dr jimbob
2
Lo siento, te equivocas en dos puntos. Primero, el OP hizo una pregunta de "prácticas de programación" que no especifica ningún lenguaje. Dijeron "método", no "función", así que supongamos algo orientado a objetos, pero eso no nos saca del ámbito de los datos numerados mágicamente. En segundo lugar, si el número mágico está integrado en la base de datos ( por ejemplo , el esquema), es aún peor tenerlo en el código. Lo correcto es obtener la magia casi como una constante de su fuente, ya sea la base de datos en sí o un módulo de esquema que centralice todas esas constantes que variarán durante la vida útil del código.
Ross Patterson el