¿Qué está mal con las cadenas mágicas?

155

Como desarrollador de software experimentado, he aprendido a evitar cadenas mágicas.

Mi problema es que hace tanto tiempo que no los uso, he olvidado la mayoría de las razones. Como resultado, tengo problemas para explicar por qué son un problema para mis colegas con menos experiencia.

¿Qué razones objetivas existen para evitarlas? ¿Qué problemas causan?

    
pregunta Kramii 05.02.2018 - 10:29

7 respuestas

203
  1. En un idioma que se compila, el valor de una cadena mágica no se verifica en el momento de la compilación . Si la cadena debe coincidir con un patrón en particular, debe ejecutar el programa para garantizar que se ajuste a ese patrón. Si usó algo como una enumeración, el valor es al menos válido en tiempo de compilación, incluso si es el valor incorrecto.

  2. Si se está escribiendo una cadena mágica en varios lugares , tiene que cambiarlos todos sin ningún tipo de seguridad (como un error de tiempo de compilación). Sin embargo, esto se puede contrarrestar solo declarándolo en un lugar y reutilizando la variable.

  3. Los errores tipográficos pueden convertirse en errores graves. Si tienes una función:

    func(string foo) {
        if (foo == "bar") {
            // do something
        }
    }
    

    y alguien escribe accidentalmente:

    func("barr");
    

    Esto es peor cuanto más rara o compleja es la cadena, especialmente si tienes programadores que no están familiarizados con el idioma nativo del proyecto.

  4. Las cadenas mágicas rara vez se autodocumentan. Si ve una cadena, eso no le dice nada de lo que podría ser la cadena. Probablemente tendrá que mirar la implementación para asegurarse de haber elegido la cadena correcta.

    Ese tipo de implementación es permeable , ya que necesita documentación externa o acceso al código para comprender lo que debe escribirse, especialmente porque tiene que ser de carácter perfecto (como en el punto 3).

  5. A falta de funciones "encontrar cadena" en los IDE, hay una pequeña cantidad de herramientas que admiten el patrón.

  6. Es posible que, por casualidad, uses la misma cadena mágica en dos lugares, cuando en realidad son cosas diferentes, así que si hiciste una búsqueda & Reemplace y cambie ambos, uno de ellos podría romperse mientras el otro funcionaba.

respondido por el Erdrik Ironrose 05.02.2018 - 11:10
88

La cumbre de lo que las otras respuestas han captado, no es que los "valores mágicos" sean malos, sino que deberían ser:

  1. definido como constantes;
  2. definido solo una vez dentro de su dominio de uso completo (si es arquitectónicamente posible);
  3. definidas juntas si forman un conjunto de constantes que están relacionadas de alguna manera;
  4. definido en un nivel apropiado de generalidad en la aplicación en la que se utilizan; y
  5. definido de tal manera que limite su uso en contextos inapropiados (por ejemplo, susceptibles de verificación de tipo).

Lo que normalmente distingue "constantes" aceptables de "valores mágicos" es una violación de una o más de estas reglas.

Si se usan bien, las constantes simplemente nos permiten expresar ciertos axiomas de nuestro código.

Lo que me lleva a un punto final, que es un uso excesivo de constantes (y, por lo tanto, un número excesivo de suposiciones o restricciones expresadas en términos de valores), incluso si cumple con los criterios anteriores (pero especialmente si se desvía de ellos), pueden implicar que la solución que se está diseñando no es lo suficientemente general o bien estructurada (y, por lo tanto, ya no estamos hablando de los pros y los contras de las constantes, sino de los pros y los contras de un código bien estructurado).

Los lenguajes de alto nivel tienen construcciones para patrones en lenguajes de nivel inferior que tendrían que emplear constantes. Los mismos patrones también se pueden usar en el lenguaje de nivel superior, pero no deberían serlo.

Pero ese puede ser un juicio experto basado en una impresión de todas las circunstancias y cómo debería ser una solución, y exactamente cómo se justificará ese juicio dependerá en gran medida del contexto. De hecho, puede que no sea justificable en términos de ningún principio general, excepto para afirmar "¡Soy lo suficientemente mayor como para haber visto este tipo de trabajo, con el que estoy familiarizado, mejor hecho"!

EDITAR: habiendo aceptado una edición, rechazando otra y habiendo realizado mi propia edición, ahora puedo considerar el formato y el estilo de puntuación de mi lista de reglas que se resolverán de una vez por todas. !

    
respondido por el Steve 05.02.2018 - 14:40
33
  • Son difíciles de rastrear.
  • Cambiar todo puede requerir cambiar múltiples archivos en posiblemente múltiples proyectos (difícil de mantener).
  • A veces es difícil decir cuál es su propósito con solo mirar su valor.
  • Sin reutilización.
respondido por el jason 05.02.2018 - 10:41
24

Ejemplo de la vida real: estoy trabajando con un sistema de terceros en el que las "entidades" se almacenan con "campos". Básicamente, se trata de un sistema EAV . Como es bastante fácil agregar otro campo, puede acceder a uno usando el nombre del campo como cadena:

Field nameField = myEntity.GetField("ProductName");

(note la cadena mágica "ProductName")

Esto puede llevar a varios problemas:

  • Necesito consultar la documentación externa para saber que "ProductName" incluso existe y su ortografía exacta
  • Además, necesito consultar ese documento para ver cuál es el tipo de datos de ese campo.
  • Los errores tipográficos en esta cadena mágica no se capturarán hasta que se ejecute esta línea de código.
  • Cuando alguien decide cambiar el nombre de este campo en el servidor (es difícil evitar el bloqueo de datos, pero no es imposible), no puedo buscar fácilmente en mi código para ver dónde debo ajustar este nombre.

Entonces, mi solución para esto fue generar constantes para estos nombres, organizados por tipo de entidad. Así que ahora puedo usar:

Field nameField = myEntity.GetField(Model.Product.ProductName);

Todavía es una cadena constante y se compila exactamente al mismo binario, pero tiene varias ventajas:

  • Después de haber escrito "Modelo", mi IDE muestra solo los tipos de entidad disponibles, por lo que puedo seleccionar "Producto" fácilmente.
  • Luego, mi IDE proporciona solo los nombres de campo que están disponibles para este tipo de entidad, también seleccionables.
  • La documentación generada automáticamente muestra el significado de este campo más el tipo de datos que se utiliza para almacenar sus valores.
  • A partir de la constante, mi IDE puede encontrar todos los lugares donde se usa esa constante exacta (a diferencia de su valor)
  • El compilador capturará los errores tipográficos. Esto también se aplica cuando se usa un modelo nuevo (posiblemente después de cambiar el nombre o eliminar un campo) para regenerar las constantes.

Siguiente en mi lista: oculte estas constantes detrás de las clases generadas fuertemente tipadas, entonces también se asegura el tipo de datos.

    
respondido por el Hans Kesting 05.02.2018 - 17:21
9

Las cuerdas mágicas no siempre son malas , por lo que esta podría ser la razón por la que no puedes encontrar una razón general para evitarlas. (Por "cadena mágica" asumo que te refieres a la cadena literal como parte de una expresión, y no definida como una constante).

En algunos casos particulares, se deben evitar las cadenas mágicas:

  • La misma cadena aparece varias veces en el código. Esto significa que podría tener un error de ortografía en uno de los lugares. Y será una molestia de los cambios de cadena. Convierta la cadena en una constante y evitará este problema.
  • La cadena puede cambiar independientemente del código donde aparece. P.ej. Si la cadena es texto que se muestra al usuario final, es probable que cambie independientemente de cualquier cambio lógico. Separar dicha cadena en un módulo separado (o configuración externa o base de datos) facilitará el cambio independiente
  • El significado de la cadena no es obvio en el contexto. En ese caso, introducir una constante hará que el código sea más fácil de entender.

Pero en algunos casos, las "cadenas mágicas" están bien. Digamos que tienes un analizador simple:

switch (token.Text) {
  case "+":
    return a + b;
  case "-":
    return a - b;
  //etc.
}

Realmente no hay magia aquí, y no se aplica ninguno de los problemas descritos anteriormente. No habría ningún beneficio en mi humilde opinión para definir string Plus="+" , etc. Manténgalo simple.

    
respondido por el JacquesB 05.02.2018 - 18:15
5

Para agregar a las respuestas existentes:

Internacionalización (i18n)

Si el texto que se muestra en la pantalla está codificado y enterrado dentro de capas de funciones, será muy difícil proporcionar traducciones de ese texto a otros idiomas.

Algunos entornos de desarrollo (por ejemplo, Qt) manejan las traducciones al buscar desde una cadena de texto de idioma base al idioma traducido. Las cadenas mágicas generalmente pueden sobrevivir a esto, hasta que decida que quiere usar el mismo texto en otro lugar y obtener un error tipográfico. Incluso entonces, es muy difícil encontrar qué cadenas mágicas necesitan ser traducidas cuando quieres agregar soporte para otro idioma.

Algunos entornos de desarrollo (por ejemplo, MS Visual Studio) adoptan otro enfoque y requieren que todas las cadenas traducidas se mantengan dentro de una base de datos de recursos y se lean para la ubicación actual mediante el ID único de esa cadena. En este caso, su aplicación con cadenas mágicas simplemente no se puede traducir a otro idioma sin un trabajo importante. El desarrollo eficiente requiere que todas las cadenas de texto se ingresen en la base de datos de recursos y se les otorgue una ID única cuando se escribe el código por primera vez, y luego es relativamente fácil. Tratar de rellenar esto después del hecho generalmente requerirá un gran esfuerzo (y sí, ¡he estado allí!), Por lo que es mucho mejor hacer las cosas bien en primer lugar.

    
respondido por el Graham 09.02.2018 - 15:58
3

Esto no es una prioridad para todos, pero si alguna vez desea poder calcular las métricas de acoplamiento / cohesión en su código de manera automatizada, las cadenas mágicas hacen esto casi imposible. Una cadena en un lugar se referirá a una clase, método o función en otro lugar, y no hay una forma automática y sencilla de determinar que la cadena está acoplada a la clase / método / función simplemente analizando el código. Solo el marco subyacente (Angular, por ejemplo) puede determinar que existe un enlace, y solo puede hacerlo en tiempo de ejecución. Para obtener la información de acoplamiento usted mismo, su analizador tendría que saber todo sobre el marco que estaba usando, más allá del lenguaje base en el que está codificando.

Pero, de nuevo, esto no es algo que les importe a muchos desarrolladores.

    
respondido por el user3511585 06.02.2018 - 16:05

Lea otras preguntas en las etiquetas