Código limpio: ¿debo cambiar el literal 1 a una constante?

14

Para evitar los números mágicos, a menudo escuchamos que debemos dar un nombre literal a significativo. Como:

//THIS CODE COMES FROM THE CLEAN CODE BOOK
for (int j = 0; j < 34; j++) {
    s += (t[j] * 4) / 5;
}

-------------------- Change to --------------------

int realDaysPerIdealDay = 4;
const int WORK_DAYS_PER_WEEK = 5;
int sum = 0;
for (int j = 0; j < NUMBER_OF_TASKS; j++) {
    int realTaskDays = taskEstimate[j] * realDaysPerIdealDay;
    int realTaskWeeks = (realdays / WORK_DAYS_PER_WEEK);
    sum += realTaskWeeks;
}

Tengo un método ficticio como este:

Explique: supongo que tengo una lista de personas para servir y, de manera predeterminada, gastamos $ 5 para comprar comida solamente, pero cuando tenemos más de una persona, necesitamos comprar agua y comida, debemos gastar más dinero, tal vez $ 6. Cambiaré mi código, concéntrese en el literal 1 , mi pregunta al respecto.

public int getMoneyByPersons(){
    if(persons.size() == 1){ 
        // TODO - return money for one person
    } else {
        // TODO - calculate and return money for people.
    }

}

Cuando le pedí a mis amigos que revisaran mi código, uno dijo que dar un nombre para el valor 1 generaría un código más limpio, y el otro dijo que no necesitamos un nombre constante aquí porque el valor es significativo en sí mismo.

Entonces, mi pregunta es ¿Debería dar un nombre para el valor literal 1? ¿Cuándo es un valor un número mágico y cuándo no? ¿Cómo puedo distinguir el contexto para elegir la mejor solución?

Jack
fuente
¿De dónde personsviene y qué describe? Su código no tiene comentarios, por lo que es difícil adivinar lo que está haciendo.
Hubert Grzeskowiak
77
Sin embargo, no se aclara más que 1. Sin embargo, cambiaría "tamaño" por "contar". Y moneyService es estúpido como es, debería poder decidir cuánto dinero devolver dependiendo de la colección de personas. Así que pasaría personas para obtener dinero y dejaría que resolviera cualquier caso excepcional.
Martin Maat
44
También puede extraer la expresión lógica de su cláusula if a un nuevo método. Tal vez lo llame IsSinglePerson (). De esta manera se extrae un poco más que apenas que una variable, sino también tomar la cláusula si un poco más fácil de leer ...
selmaohneh
2
¿Quizás esta lógica sería más adecuada en moneyService.getMoney ()? ¿Alguna vez habrá un momento en el que deba llamar a getMoney en el caso de 1 persona? Pero estaría de acuerdo con el sentimiento general de que 1 es claro. Los números mágicos son números en los que tendrías que rascarte la cabeza preguntando cómo llegó el programador a ese número ... es decirif(getErrorCode().equals(4095)) ...
Neil

Respuestas:

23

No. En ese ejemplo, 1 es perfectamente significativo.

Sin embargo, ¿qué pasa si persons.size () es cero? Parece extraño que persons.getMoney()funcione para 0 y 2 pero no para 1.

user949300
fuente
Estoy de acuerdo en que 1 es significativo. Gracias, por cierto, actualicé mi pregunta. Puedes verlo nuevamente para entender mi idea.
Jack
3
Una frase que he escuchado varias veces a lo largo de los años es que los números mágicos son constantes sin nombre distintas de 0 y 1. Si bien puedo pensar en ejemplos en los que se deben nombrar estos números, es una regla bastante simple seguir el 99% de las hora.
Baldrickk
@Baldrickk A veces, otros literales numéricos tampoco deben ocultarse detrás de un nombre.
Deduplicador el
@Deduplicator ¿te vienen a la mente algunos ejemplos?
Baldrickk
@Baldrickk Ver amon .
Deduplicador
18

¿Por qué un fragmento de código contiene ese valor literal en particular?

  • ¿Este valor tiene un significado especial en el dominio del problema ?
  • ¿O es este valor solo un detalle de implementación , donde ese valor es una consecuencia directa del código circundante?

Si el valor literal tiene un significado que no está claro por el contexto, entonces sí, dar un nombre a ese valor a través de una constante o variable es útil. Más tarde, cuando se olvida el contexto original, el código con nombres de variables significativos será más fácil de mantener. Recuerde, la audiencia para su código no es principalmente el compilador (el compilador trabajará felizmente con un código horrible), sino los futuros mantenedores de ese código, que apreciarán si el código se explica por sí mismo.

  • En su primer ejemplo, el significado de literales tales como 34, 4, 5no es evidente por el contexto. En cambio, algunos de estos valores tienen un significado especial en el dominio del problema. Por lo tanto, fue bueno darles nombres.

  • En su segundo ejemplo, el significado del literal 1es muy claro desde el contexto. Introducir un nombre no es útil.

De hecho, introducir nombres para valores obvios también puede ser malo, ya que oculta el valor real.

  • Esto puede ocultar errores si el valor nombrado se modifica o es incorrecto, especialmente si la misma variable se reutiliza en partes de código no relacionadas.

  • Un código también puede funcionar bien para un valor específico, pero puede ser incorrecto en el caso general. Al introducir abstracciones innecesarias, el código ya no es obviamente correcto.

No hay límite de tamaño en literales "obvios" porque esto depende totalmente del contexto. Por ejemplo, el literal 1024puede ser totalmente obvio en el contexto del cálculo del tamaño del archivo, o el literal 31en el contexto de una función hash, o el literal padding: 0.5emen el contexto de una hoja de estilo CSS.

amon
fuente
8

Hay varios problemas con este código que, por cierto, se pueden acortar así:

public List<Money> getMoneyByPersons() {
    return persons.size() == 1 ?
        moneyService.getMoneyIfHasOnePerson() :
        moneyService.getMoney(); 
}
  1. No está claro por qué una persona es un caso especial. Supongo que hay una regla comercial específica que dice que obtener dinero de una persona es radicalmente diferente de obtener dinero de varias personas. Sin embargo, tengo que ir y mirar dentro de ambos getMoneyIfHasOnePersony getMoney, con la esperanza de entender por qué hay casos distintos.

  2. El nombre getMoneyIfHasOnePersonno se ve bien. Por el nombre, esperaría el método para verificar si hay una sola persona y, si este es el caso, obtener dinero de él; de lo contrario, no hagas nada. Desde su código, esto no es lo que está sucediendo (o está haciendo la condición dos veces).

  3. ¿Hay alguna razón para devolver List<Money>una colección en lugar de una colección?

Volviendo a su pregunta, dado que no está claro por qué hay un tratamiento especial para una persona, el dígito debe reemplazarse por una constante, a menos que haya otra forma de hacer explícitas las reglas. Aquí, uno no es muy diferente de cualquier otro número mágico. Podría tener reglas comerciales que indiquen que el tratamiento especial se aplica a una, dos o tres personas, o solo a más de doce personas.

¿Cómo puedo distinguir el contexto para elegir la mejor solución?

Haces lo que haga que tu código sea más explícito.

Ejemplo 1

Imagine la siguiente pieza de código:

if (sequence.size() == 0) {
    return null;
}

return this.processSequence(sequence);

¿Es cero aquí un valor mágico? El código es bastante claro: si no hay elementos en la secuencia, no lo procesemos y devolveremos un valor especial. Pero este código también se puede reescribir así:

if (sequence.isEmpty()) {
    return null;
}

return this.processSequence(sequence);

Aquí, no más constante, y el código es aún más claro.

Ejemplo 2

Toma otra pieza de código:

const result = Math.round(input * 1000) / 1000;

No toma mucho tiempo entender lo que hace en lenguajes como JavaScript que no tienen round(value, precision)sobrecarga.

Ahora, si quieres introducir una constante, ¿cómo se llamaría? El término más cercano que puede obtener es Precision. Entonces:

const precision = 1000;
const result = Math.round(input * precision) / precision;

¿Mejora la legibilidad? Puede ser. Aquí, el valor de una constante es bastante limitado, y puede preguntarse si realmente necesita realizar la refactorización. Lo bueno aquí es que ahora, la precisión se declara solo una vez, por lo que si cambia, no corre el riesgo de cometer un error como:

const result = Math.round(input * 100) / 1000;

cambiando el valor en una ubicación y olvidando hacerlo en la otra.

Ejemplo 3

A partir de esos ejemplos, puede tener la impresión de que los números deben reemplazarse por constantes en todos los casos . Esto no es verdad. En algunas situaciones, tener una constante no conduce a la mejora del código.

Tome el siguiente código:

class Point
{
    ...
    public void Reset()
    {
        x, y = (0, 0);
    }
}

Si intenta reemplazar ceros por una variable, la dificultad sería encontrar un nombre significativo. ¿Cómo lo nombrarías? ZeroPosition? Base? Default? Introducir una constante aquí no mejoraría el código de ninguna manera. Lo haría un poco más largo, y solo eso.

Tales casos son, sin embargo, raros. Por lo tanto, cada vez que encuentre un número en el código, intente encontrar cómo se puede refactorizar el código. Pregúntese si hay un significado comercial para el número. En caso afirmativo, una constante es obligatoria. Si no, ¿cómo nombrarías el número? Si encuentra un nombre significativo, eso es genial. Si no, es probable que encuentre un caso en el que la constante sea innecesaria.

Arseni Mourzenko
fuente
Yo agregaría 3a: no use la palabra dinero en nombres variables, use cantidad, saldo o similares. Una recaudación de dinero no tiene sentido, las recaudaciones de cantidades o saldos sí. (OK, tengo una pequeña colección de dinero extranjero (o más bien monedas) pero eso es un asunto diferente que no es de programación).
Doblado
3

Podría hacer una función que tome un solo parámetro y devuelva cuatro veces dividido por cinco que ofrezca un alias limpio de lo que hace mientras sigue usando el primer ejemplo.

Solo estoy ofreciendo mi propia estrategia habitual, pero quizás también aprenda algo.

Lo que estoy pensando es.

// Just an example name  
function normalize_task_value(task) {  
    return (task * 4) / 5;  // or to `return (task * 4) / NUMBER_OF_TASKS` but it really matters what logic you want to convey and there are reasons to many ways to make this logical. A comment would be the greatest improvement

}  

// Is it possible to just use tasks.length or something like that?  
// this NUMBER_OF_TASKS is the only one thats actually tricky to   
// understand right now how it plays its role.  

function normalize_all_task_values(tasks, accumulator) {  
    for (int i = 0; i < NUMBER_OF_TASKS; i++) {  
        accumulator += normalize_task_value(tasks[i]);
    }
}  

Lo siento si no estoy en la base pero solo soy un desarrollador de JavaScript. No estoy seguro de qué composición justifiqué esto, supongo que no todo tiene que estar en una matriz o lista, pero .length tendría mucho sentido. Y el * 4 haría que el bien sea constante ya que su origen es nebuloso.

etisdew
fuente
55
Pero, ¿qué significan esos valores 4 y 5? Si alguna de ellas necesita cambiar, ¿podría encontrar todos los lugares donde se usa el número en un contexto similar y actualizar esas ubicaciones en consecuencia? Ese es el quid de la pregunta original.
Bart van Ingen Schenau
Creo que el primer ejemplo fue bastante explicativo, por lo que podría no ser uno que pueda responder. La operación no debería cambiar en el futuro, cuando lo necesite, escribiría una nueva para lograr lo que se necesita. Los comentarios son lo que creo que más beneficiarían este propósito. Es una llamada de una línea para reducir en mi idioma. ¿Qué tan importante es hacer absolutamente entendible para el laico?
etisdew
@BartvanIngenSchenau Toda la función tiene un nombre incorrecto. Preferiría un mejor nombre de función, un comentario con una especificación de lo que se supone que debe devolver la función, y un comentario de por qué * 4/5 logra eso. Los nombres constantes no son realmente necesarios.
gnasher729
@ gnasher729: Si las constantes aparecen exactamente una vez en el código fuente de una función bien nombrada y bien documentada, entonces podría no ser necesario usar una constante con nombre. Tan pronto como una constante aparezca varias veces con el mismo significado, darle un nombre a esa constante asegura que no tenga que averiguar si todas esas instancias del literal 42 significan lo mismo o no.
Bart van Ingen Schenau
En el fondo, si espera que necesite cambiar el valor, sí. Sin embargo, lo cambiaría a una constante para representar la variable. No creo que sea el caso y esto debería ser simplemente una variable intermedia que reside en el alcance por encima. No quiero escapar y decir que todo sea un parámetro para una función, pero si puede cambiar, pero rara vez lo convertiría en un parámetro interno para esa función o el alcance del objeto / estructura donde está trabajando actualmente para hacer ese cambio dejando El alcance global solo.
etisdew
2

Ese número 1, ¿podría ser un número diferente? ¿Podría ser 2 o 3, o hay razones lógicas por las que debe ser 1? Si debe ser 1, entonces usar 1 está bien. De lo contrario, puede definir una constante. No llames a eso constante UNO. (Lo he visto hecho).

60 segundos en un minuto: ¿necesitas una constante? Bueno, son 60 segundos, no 50 o 70. Y todos lo saben. Entonces eso puede seguir siendo un número.

60 elementos impresos por página: ese número podría haber sido fácilmente 59 o 55 o 70. En realidad, si cambia el tamaño de la fuente, podría convertirse en 55 o 70. Entonces, aquí se pide más una constante significativa.

También es una cuestión de qué tan claro es el significado. Si escribe "minutos = segundos / 60", está claro. Si escribe "x = y / 60", eso no está claro. Debe haber algunos nombres significativos en alguna parte.

Hay una regla absoluta: no hay reglas absolutas. Con la práctica, descubrirá cuándo usar números y cuándo usar constantes con nombre. No lo hagas porque un libro lo dice, hasta que entiendas por qué dice eso.

gnasher729
fuente
"60 segundos en un minuto ... Y todos lo saben. Así que eso puede seguir siendo un número". - No es un argumento válido para mí. ¿Qué sucede si tengo 200 lugares en mi código donde se usa "60"? ¿Cómo encuentro los lugares donde se usa como segundos por minuto frente a otros usos posiblemente igualmente "naturales"? - En la práctica, sin embargo, yo también me atrevo a usar minutes*60, a veces incluso hours*3600cuando lo necesito sin declarar constantes adicionales. Durante días probablemente escribiría d*24*3600o d*24*60*60porque 86400está cerca del borde donde alguien no reconocerá ese número mágico de un vistazo.
JimmyB
@JimmyB Si está usando "60" en 200 lugares en su código, es muy probable que la solución esté refactorizando una función en lugar de introducir una constante.
klutt
0

He visto una buena cantidad de código como en el OP (modificado) en las recuperaciones de DB. La consulta devuelve una lista, pero las reglas de negocio dicen que solo puede haber un elemento. Y luego, por supuesto, algo cambió para 'solo este caso' a una lista con más de un elemento. (Sí, dije bastantes veces. Es casi como si ... nm)

Entonces, en lugar de crear una constante, crearía (en un método de código limpio) un método para dar un nombre o aclarar lo que el condicional pretende detectar (y encapsular cómo lo detecta):

public int getMoneyByPersons(){
  if(isSingleDepartmentHead()){ 
    // TODO - return money for one person
  } else {
    // TODO - calculate and return money for people.
  }
}

public boolean isSingleDepartmentHead() {
   return persons.size() == 1;
}
Kristian H
fuente
Mucho preferible para unificar el manejo. Una lista de n elementos se puede tratar de manera uniforme, sea lo que sea n.
Deduplicador el
He visto que no es así, donde el caso único era, de hecho, un valor diferente (marginal) de lo que hubiera sido si el mismo usuario hubiera sido parte de una lista de tamaño n. En un OO bien hecho, esto podría no ser un problema, pero, cuando se trata de sistemas heredados, una buena implementación de OO no siempre es factible. Lo mejor que obtuvimos fue una fachada OO razonable en la lógica DAO.
Kristian H