Eliminando los números mágicos: ¿Cuándo es el momento de decir "No"?

36

Todos somos conscientes de que los números mágicos (valores codificados) pueden causar estragos en su programa, especialmente cuando es hora de modificar una sección de código que no tiene comentarios, pero ¿dónde dibuja la línea?

Por ejemplo, si tiene una función que calcula el número de segundos entre dos días, ¿reemplaza

seconds = num_days * 24 * 60 * 60

con

seconds = num_days * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE

¿En qué momento decide que es completamente obvio lo que significa el valor codificado y lo deja en paz?

oosterwal
fuente
2
¿Por qué no reemplazar ese cálculo con una función o macro para que su código termine pareciendoseconds = CALC_SECONDS(num_days);
FrustratedWithFormsDesigner
15
TimeSpan.FromDays(numDays).Seconds;
Nadie
18
@oosterwal: con esa actitud ( HOURS_PER_DAY will never need to be altered), nunca codificará el software implementado en Marte. : P
FrustratedWithFormsDesigner
23
Hubiera reducido el número de constantes a solo SECONDS_PER_DAY = 86400. ¿Por qué calcular algo que no cambiará?
JohnFx
17
¿Qué pasa con los segundos bisiestos?
John

Respuestas:

40

Hay dos razones para usar constantes simbólicas en lugar de literales numéricos:

  1. Para simplificar el mantenimiento si cambian los números mágicos. Esto no se aplica a su ejemplo. Es extremadamente improbable que cambie la cantidad de segundos en una hora o la cantidad de horas en un día.

  2. Para mejorar la lectura. La expresión "24 * 60 * 60" es bastante obvia para casi todos. "SECONDS_PER_DAY" también lo es, pero si está buscando un error, es posible que deba verificar que SECONDS_PER_DAY se haya definido correctamente. Hay valor en la brevedad.

Para los números mágicos que aparecen exactamente una vez y son independientes del resto del programa, decidir si crear un símbolo para ese número es cuestión de gustos. Si hay alguna duda, continúe y cree un símbolo.

No hagas esto:

public static final int THREE = 3;
Kevin Cline
fuente
3
+1 @kevin cline: Estoy de acuerdo con tu punto sobre la brevedad y la caza de insectos. El beneficio adicional que veo al usar una constante con nombre, especialmente cuando se depura, es que si se descubre que una constante se definió incorrectamente, solo necesita cambiar una pieza de código en lugar de buscar en todo un proyecto todas las ocurrencias de la implementación incorrecta valor.
oosterwal
40
O peor aún:publid final int FOUR = 3;
gablin
3
Dios mío, debes haber trabajado con el mismo tipo con el que trabajé una vez.
rapid_now
2
@gablin: Para ser justos, es bastante útil que los pubs tengan tapas.
Alan Pearce
10
He visto esto: public static int THREE = 3;... nota: final¡ no !
Stephen C
29

Mantendría la regla de nunca tener números mágicos.

Mientras

seconds = num_days * 24 * 60 * 60

Es perfectamente legible la mayor parte del tiempo, después de haber codificado durante 10 horas al día durante tres o cuatro semanas en modo crujiente

seconds = num_days * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE

Es mucho más fácil de leer.

La sugerencia de FrustratedWithFormsDesigner es mejor:

seconds = num_days * DAYS_TO_SECOND_FACTOR

o mejor

seconds = CONVERT_DAYS_TO_SECONDS(num_days)

Las cosas dejan de ser obvias cuando estás muy cansado. Código defensivo .

Vitor Py
fuente
13
Entrar en un modo crujiente como usted describe es un antipatrón contraproducente que debe evitarse. Los programadores alcanzan una productividad sostenida máxima de aproximadamente 35-40 horas / semana.
btilly
44
@btilly Estoy totalmente de acuerdo contigo. Pero sucede, a menudo debido a factores externos.
Vitor Py
3
En general defino constantes para segundo, minuto, día y hora. Si nada más, '30 * MINUTO 'es realmente fácil de leer y sé que es un momento sin tener que pensarlo.
Zachary K
99
@btilly: El pico es de 35-40 horas o un nivel de alcohol en la sangre entre 0.129% y 0.138%. Lo leí en XKCD , ¡así que tiene que ser cierto!
oosterwal
1
Si vi una constante como HOURS_PER_DAY, la eliminaría y luego te humillaría públicamente frente a tus compañeros. Ok, tal vez renunciaría a la humillación pública, pero probablemente la eliminaría.
Ed S.
8

El momento de decir que no es casi siempre. Los momentos en los que encuentro que es más fácil usar números codificados es en lugares como el diseño de la interfaz de usuario: crear una constante para el posicionamiento de cada control en el formulario se vuelve muy cubmersone y agotador, y si ese código generalmente lo maneja un diseñador de interfaz de usuario, no importa mucho ... a menos que la IU se presente dinámicamente, o use posiciones relativas a algún ancla o se escriba a mano. En ese caso, diría que es mejor definir algunas constantes significativas para el diseño. Y si necesita un factor fudge aquí o allá para alinear / posicionar algo "justo", eso también debe definirse.

Pero en su ejemplo, creo que reemplazar 24 * 60 * 60por DAYS_TO_SECONDS_FACTORes mejor.


Admito que los valores codificados también están bien cuando el contexto y el uso son completamente claros. Esto, sin embargo, es un juicio ...

Ejemplo:

Como señaló @rmx, usar 0 o 1 para verificar si una lista está vacía, o tal vez en los límites de un bucle, es un ejemplo de un caso en el que el propósito de la constante es muy claro.

FrustratedWithFormsDesigner
fuente
2
Por lo general, está bien usarlo 0o 1creo. if(someList.Count != 0) ...es mejor que if(someList.Count != MinListCount) .... No siempre, pero en general.
Nadie
2
@Dima: el diseñador de formularios VS se encarga de todo eso. Si quiere crear constantes, está bien para mí. Pero no voy a entrar en el código generado y reemplazar todos los valores codificados con constantes.
FrustratedWithFormsDesigner
44
Sin embargo, no confundamos el código destinado a ser generado y manejado por una herramienta con código escrito para consumo humano.
biziclop
1
@FrustratedWithFormsDesigner como señaló @biziclop, el código generado es un animal completamente diferente. Las constantes nombradas deben usarse absolutamente en el código que las personas leen y modifican. El código generado, al menos en el caso ideal, no debe modificarse en absoluto.
Dima
2
@FrustratedWithFormsDesigner: ¿Qué sucede cuando tiene un valor bien conocido codificado en docenas de archivos en su programa que de repente necesita ser cambiado? Por ejemplo, codifica el valor que representa el número de tics de reloj por microsegundo para un procesador incorporado y luego se le indica que transfiera su software a un diseño donde haya un número diferente de tics de reloj por microsegundo. Si su valor hubiera sido algo común, como 8, realizar una búsqueda / reemplazo en docenas de archivos podría terminar causando más problemas.
oosterwal
8

Detente cuando no puedas precisar un significado o un propósito para el número.

seconds = num_days * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE

es mucho más fácil de leer que simplemente usar los números. (Aunque podría hacerse más legible al tener una sola SECONDS_PER_DAYconstante, pero es un problema completamente separado).

Suponga que un desarrollador que mira el código puede ver lo que hace. Pero no asuma que ellos también saben por qué. Si su constante ayuda a entender el por qué, hágalo. Si no, no lo hagas.

Si termina con demasiadas constantes, como sugirió una respuesta, considere usar un archivo de configuración externo, ya que tener docenas de constantes en un archivo no mejora exactamente la legibilidad.

biziclop
fuente
7

Probablemente diría "no" a cosas como:

#define HTML_END_TAG "</html>"

Y definitivamente diría "no" a:

#define QUADRATIC_DISCRIMINANT_COEF 4
#define QUADRATIC_DENOMINATOR_COEF  2
dan04
fuente
7

Uno de los mejores ejemplos que he encontrado para promover el uso de constantes para cosas obvias como HOURS_PER_DAYes:

Estábamos calculando cuánto tiempo estuvieron las cosas en la cola de trabajo de una persona. Los requisitos se definieron en términos generales y el programador codificó 24en varios lugares. Finalmente, nos dimos cuenta de que no era justo castigar a los usuarios por estar sentados en un problema durante 24 horas, cuando realmente solo funcionan 8 horas al día. Cuando llegó la tarea de arreglar esto Y ver qué otros informes podrían tener el mismo problema, fue bastante difícil buscar / buscar en el código para 24 hubiera sido mucho más fácil de buscar / buscarHOURS_PER_DAY

bkulyk
fuente
oh no, así que las horas por día varían según se refiera a las horas de trabajo por día (yo trabajo 7.5 BTW) o las horas en un día. Para cambiar el significado de la constante de esa manera, querrás reemplazar su nombre por otro. Sin embargo, su punto sobre la búsqueda facilitada es válido.
gbjbaanb
2
Parece que en este caso HOURS_PER_DAY tampoco era realmente la constante deseada. Pero poder buscarlo por su nombre es un gran beneficio, incluso si (o especialmente si) necesita cambiarlo a otra cosa en muchos lugares.
David K
4

Creo que mientras el número sea completamente constante y no tenga posibilidad de cambiar, es perfectamente aceptable. Entonces, en su caso, seconds = num_days * 24 * 60 * 60está bien (suponiendo, por supuesto, que no hace algo tonto como hacer este tipo de cálculo dentro de un ciclo) y podría decirse que es mejor para la legibilidad que seconds = num_days * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE.

Cuando haces cosas como esta es malo:

lineOffset += 24; // 24 lines to a page

Incluso si no puede ajustar más líneas en la página o incluso si no tiene intenciones de cambiarla, use una variable constante en su lugar, porque algún día volverá a perseguirlo. En última instancia, el punto es la legibilidad, no guardar 2 ciclos de cálculo en la CPU. Esto ya no es 1978 cuando se apretaron los preciosos bytes para todo su valor.

Neil
fuente
2
¿Le parece aceptable codificar el valor inalterable 86400 en lugar de usar la constante SECONDS_PER_DAY nombrada? ¿Cómo verificaría que todas las apariciones del valor fueran correctas y que no faltaran un 0 o intercambiaran el 6 y el 4, por ejemplo?
oosterwal
Entonces, ¿por qué no: segundos = num_days * 86400? Eso tampoco cambiará.
JeffO
2
He hecho lo SECONDS_PER_DAY en ambos sentidos: usando el nombre y el número. Cuando vuelve al código 2 años después, el número nombrado SIEMPRE tiene más sentido.
rapid_now
2
segundos = num_days * 86400 no me queda claro. Eso es lo que finalmente cuenta. Si veo "segundos = num_days * 24 * 60 * 60", aparte del hecho de que los nombres de las variables prestan bastante bien el significado en este caso, inmediatamente me pregunto por qué los separé y el significado se hace obvio porque me fui ellos como números (por lo tanto, son constantes), no variables a las cuales una investigación adicional requeriría que comprenda sus valores y si son constantes.
Neil
1
Lo que la gente a menudo no se da cuenta: si cambia el valor de la línea Offset de 24 a 25, tendrá que revisar todo el código para ver dónde se usó 24 y si es necesario cambiar, y luego todos los cálculos de días a horas multiplicando por 24 realmente se interpone en tu camino.
gnasher729
3
seconds = num_days * 24 * 60 * 60

Está perfectamente bien. Estos no son realmente números mágicos, ya que nunca cambiarán.

Cualquier número que pueda cambiar razonablemente o que no tenga un significado obvio debe ponerse en variables. Lo que significa casi todos ellos.

Carra
fuente
2
¿ seconds = num_days * 86400Aún sería aceptable? Si un valor como ese se usara varias veces en muchos archivos diferentes, ¿cómo verificaría que alguien no haya escrito accidentalmente seconds = num_days * 84600en uno o dos lugares?
oosterwal
1
Escribir 86400 es muy diferente de escribir 24 * 60 * 60.
Carra
44
Por supuesto que va a cambiar. No todos los días tienen 86,400 segundos. Considere, por ejemplo, el horario de verano. Una vez al año, algunos lugares solo tienen 23 horas en un día, y otro día tendrán 25. poof sus números están rotos.
Dave DeLong
1
@Dave, excelente punto. Existen segundos de salto - en.wikipedia.org/wiki/Leap_second
Punto justo. Agregar una función sería una salvaguarda si alguna vez necesita detectar esas excepciones.
Carra
3

Evitaría crear constantes (valores mágicos) para convertir un valor de una unidad a otra. En caso de convertir, prefiero un nombre de método de voz. En este ejemplo, esto sería, por ejemplo, DayToSeconds(num_days)interno, el método no necesita valores mágicos porque el significado de "24" y "60" está claro.

En este caso nunca usaría segundos / minutos / horas. Solo usaría TimeSpan / DateTime.

KayS
fuente
1

Use el contexto como parámetro para decidir

Por ejemplo, tiene una función llamada "CalculateSecondsBetween: aDay y: anotherDay", no necesitará hacer mucha explicación sobre lo que hacen esos números, porque el nombre de la función es bastante representativo.

Y otra pregunta es, ¿cuáles son las posibilidades de calcularlo de una manera diferente? A veces hay muchas maneras de hacer lo mismo, así que para guiar a los futuros programadores y mostrarles qué método usó, definir constantes podría ayudar a resolverlo.

Guiman
fuente