Saltar al contenido

Blog técnico | Evitar los números mágicos

Los números mágicos son un anti-patrón y generalmente deben ser evitados. ¿Qué quiero decir cuando digo número mágico? Me refiero a usar los números directamente en el código en lugar de usar una constante nombrada. Esto también puede aplicarse a otros tipos de datos y literales, especialmente a las cadenas. ¿Por qué son malos? Inhiben la legibilidad y la refactorización.

Legibilidad

Una de las grandes ventajas de eliminar los números mágicos es que aumenta la legibilidad y ayuda a que tu código sea más auto-documentado. Nos permite pensar en el conocimiento que el número representa en lugar de preocuparnos por el número en sí. Además, cuando se utiliza un número mágico, la intención o el significado de ese número se ofusca y puede ser poco claro.

Blog técnico | Evitar los números mágicos
Blog técnico | Evitar los números mágicos

Considere este fragmento de código Swift:

funchaveICaughtThemAll(numberCaught:Int)- &gt Bool{returnnnumberCaught==151}funchowManyMoreDoIHaveToCatch(numberCaught:Int)--- Int{return151-numberCaught}

Puede que no esté inmediatamente claro lo que significa el número mágico 151 o por qué fue elegido, especialmente si no estás familiarizado con el dominio del problema (Pokemon). Al extraer el 151 en una constante con nombre, el código es ahora más fácil de leer y no tienes que razonar sobre (o peor, adivinar) lo que significa el número 151.

lettotalPokemon=151funchaveICaughtThemAll(numberCaught:Int)- &gt Bool{returnnumberCaught==totalPokemon}funchowManyMoreDoIHaveToCatch(numberCaught:Int)- &gtInt{returntotalPokemon-numberCaught}

Refactoriabilidad

La otra gran ventaja de evitar los números mágicos es que hace que el código sea más fácil de refacturar y menos propenso a los bichos. Lo logra ayudándonos a mantener el principio de SECO: «Cada pieza de conocimiento debe tener una representación única, inequívoca y autoritaria dentro de un sistema».

Volvamos al ejemplo anterior.

lettotalPokemon=151funchaveICaughtThemAll(numberCaught:Int)- &gt Bool{returnnumberCaught==totalPokemon}funchowManyMoreDoIHaveToCatch(numberCaught:Int)- &gtInt{returntotalPokemon-numberCaught}

Si el valor de totalPokemon cambia alguna vez (alerta de spoiler: lo hará), ahora sólo tienes que cambiarlo en un lugar. En el ejemplo original, tendrías que encontrar todas las instancias del número 151 y reemplazarlas. Puede que no sea tan difícil en este ejemplo, pero en una base de código real, los usos de este número mágico puede que no estén cerca unos de otros y puede que te olvides de hacer un hallazgo y reemplazar a través de tu base de código o puede que ni siquiera pienses que lo necesitas. O, ¿qué pasa si tienes otra instancia del número 151 en tu código, pero esa instancia representa el número máximo de puntos de golpe que un Pokémon puede tener? Si simplemente haces un hallazgo y lo reemplazas en toda tu base de datos, cambiarás ese 151 erróneamente. Al usar una constante para totalPokemon, ahora tienes «una representación única, inequívoca y autorizada» de este conocimiento en tu sistema. Ahora, cuando alguien decide que 151 no es suficiente Pokemon, sólo tienes que cambiar esa pieza de conocimiento en un lugar, previniendo así los errores en todo tu sistema.

Nombrar y duplicar valores

A medida que elimine los números mágicos de su código y los reemplace por constantes con nombre, asegúrese de que su constante realmente SÍ aumenta la legibilidad. Haciendo esto:

letseven=7

no es útil. No da información adicional sobre lo que representa el conocimiento 7. Esto nos lleva a otro caso a considerar:

letstartingNumberOfCards=7letmaxNumberOfPlayers=7

Aunque 7 es el valor de ambas constantes, representa un conocimiento diferente en contextos diferentes y, por lo tanto, 2 constantes de nombre diferente son apropiadas.

Excepciones

Como con la mayoría de las «reglas» de la programación, hay excepciones.

0, 1 y nulo/nulo suelen ser seguros para ser usados por sí mismos. Por ejemplo:

array.count==0array[i+1]middleName==nilfirstName==""

Basado en el contexto en el que estos literales son usados, el significado es claro. De hecho, el significado puede ser ofuscado si se utiliza una constante con nombre. Si el primer ejemplo fuera array.count == emptyArrayCount, esto sugeriría que consideras una matriz «vacía» si tiene algún número de elementos en ella que no sea 0. Si ese es el caso, entonces una constante con nombre puede ser apropiada. Sin embargo, si has dejado emptyArrayAmount = 0, probablemente sólo confundirá a la gente que lea tu código.

Las cuerdas que se utilizan para el registro o el rastreo también son una excepción; de hecho, a menudo es preferible mantenerlas como literales. El principio general es el siguiente: si el significado de un literal está claro en su contexto y no cambiará en el futuro, probablemente esté bien utilizarlo. Sin embargo, eso nos lleva a un área gris.

Área gris

Aquí hay una pregunta: ¿deberías usar una constante como let secondsPerMinute = 60? Este valor no va a cambiar pronto, así que te preocupas menos por la refactorización. Yo diría que depende del contexto. En muchos contextos, secondsPerMinute eliminará cualquier ambigüedad sobre lo que significa el número mágico 60. Por ejemplo, ¿significa 60 segundos por minuto o minutos por hora? Sin embargo, si el único uso de secondsPerMinute es en un cálculo para el número de segundos en un día de trabajo, entonces algo como

lethoursInTheWorkday=8letsecondsInTheWorkday=hoursInTheWorkDay*60*60

es probablemente más legible que

lethoursInTheWorkday=8letminutesPerHour=60letseconsecuenciasPerMinute=60letseconsecuenciasInTheWorkday=horasInTheWorkday*minutesPerHour*seconsecuenciasPerMinute

especialmente si no estás usando «secondsPerMinute» en ningún otro sitio. Note que también nos basamos en el hecho de que 60 segundos/minuto y 60 minutos/hora son piezas de conocimiento bastante universales. Algo como radiansPerDegree en el siguiente código podría ser más apropiado porque no es tan conocido de una constante:

letradianosPerDegree=0.0174533letradianosEnCirculo=360*radiantesPerDegree

Además, especificando constantes matemáticas como esta u otras como pi asegura la consistencia en sus cálculos a través de su código. No querrías 3.14159 en un lugar y 3.14 en otro, especialmente si estás escribiendo código para un cohete u otro dominio donde la alta precisión es importante.

Conclusión

Aunque evitar los números mágicos es un concepto simple, aumentará la legibilidad y la capacidad de refactorización de su código, mejorando así su limpieza. Aunque este post proporciona algunas pautas, sobre todo, sea pragmático y recuerde que el contexto importa. Aunque los números mágicos deben ser evitados, no los borre dogmáticamente en su código sin pensarlo.

Categorías: technicalTags: swift, clean code