¿Qué hay de malo con las cuerdas mágicas?

164

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

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

¿Qué razones objetivas hay para evitarlos? ¿Qué problemas causan?

Kramii
fuente
38
¿Qué es una cuerda mágica? ¿Lo mismo que los números mágicos ?
Laiv
14
@Laiv: Son similares a los números mágicos, sí. Me gusta la definición en deviq.com/magic-strings : "Las cadenas mágicas son valores de cadena que se especifican directamente dentro del código de la aplicación y que tienen un impacto en el comportamiento de la aplicación". (La definición en en.wikipedia.org/wiki/Magic_string no es lo que tengo en mente)
Kramii
17
Esto es divertido , he aprendido a detestar ... más tarde ¿Qué argumentos puedo usar para persuadir a mis juniors ... La historia interminable :-). No trataría de "convencer" sino que preferiría que aprendieran por su cuenta. Nada dura más que una lección / idea alcanzada por su propia experiencia. Lo que estás tratando de hacer es adoctrinar . No hagas eso a menos que quieras un equipo de Lemmings.
Laiv
15
@Laiv: Me encantaría dejar que las personas aprendan de su propia experiencia, pero desafortunadamente esa no es una opción para mí. Trabajo para un hospital financiado con fondos públicos donde los errores sutiles pueden comprometer la atención del paciente y no podemos pagar los costos de mantenimiento evitables.
Kramii
66
@DavidArno, eso es exactamente lo que está haciendo al hacer esta pregunta.
user56834

Respuestas:

211
  1. En un lenguaje que 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 puede ser el valor incorrecto.

  2. Si una cadena mágica se escribe en varios lugares , debe cambiarlos todos sin ningún tipo de seguridad (como un error en tiempo de compilación). Sin embargo, esto puede contrarrestarse declarándolo solo 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 sea la cadena, especialmente si tiene programadores que no están familiarizados con el idioma nativo del proyecto.

  4. Las cadenas mágicas rara vez se documentan por sí mismas. Si ve una cadena, eso no le dice nada de qué otra cosa podría / debería ser la cadena. Probablemente tendrá que investigar la implementación para asegurarse de haber elegido la cadena correcta.

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

  5. Además de las funciones de "buscar cadena" en IDEs, hay una pequeña cantidad de herramientas que admiten el patrón.

  6. Por coincidencia, puedes usar la misma cuerda mágica en dos lugares, cuando en realidad son cosas diferentes, por lo que si hiciste Buscar y reemplazar y cambiaste ambas, una de ellas podría romperse mientras la otra funcionaba.

Erdrik Ironrose
fuente
34
Con respecto al primer argumento: TypeScript es un lenguaje compilado que puede verificar los literales de cadena. Esto también invalida el argumento dos a cuatro. Por lo tanto, el problema no son las cadenas en sí, sino el uso de un tipo que permita demasiados valores. El mismo razonamiento se puede aplicar al uso de enteros mágicos para las enumeraciones.
Yogu
11
Como no tengo experiencia con TypeScript, diferiré su opinión allí. Lo que diría entonces es que las cadenas sin marcar (como es el caso de todos los idiomas que he usado) son el problema.
Erdrik Ironrose
23
@Yogu Typecript no cambiará el nombre de todas sus cadenas por usted si cambia el tipo literal de cadena estática que espera. Obtendrá errores de tiempo de compilación para ayudarlo a encontrarlos todos, pero eso es solo una mejora parcial en 2. Sin decir que es nada menos que absolutamente sorprendente (porque es eso, y me encanta la función), pero definitivamente no. eliminar por completo la ventaja de las enumeraciones. En nuestro proyecto, cuándo usar enumeraciones y cuándo no seguir siendo una especie de pregunta de estilo abierto de la que no estamos seguros; ambos enfoques tienen molestias y ventajas.
KRyan
30
Uno grande que he visto no para cadenas tanto como números, pero podría suceder con cadenas, es cuando tienes dos valores mágicos con el mismo valor. Entonces uno de ellos cambia. Ahora está pasando por un código que cambia el valor anterior al nuevo valor, que es trabajo por sí solo, pero también está haciendo un trabajo EXTRA para asegurarse de no cambiar los valores incorrectos. Con variables constantes, no solo no tiene que pasar por ello manualmente, sino que no le preocupa que haya cambiado algo incorrecto.
corsiKa
35
@Yogu Diría además que si el valor de un literal de cadena se verifica en tiempo de compilación, deja de ser una cadena mágica . En ese punto, es solo un valor const / enum normal que se escribe de una manera divertida. Dada esa perspectiva, en realidad diría que su comentario realmente apoya los puntos de Erdrik, en lugar de refutarlos.
GrandOpener
89

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 reconociblemente como constantes;
  2. definido solo una vez dentro de todo su dominio de uso (si es arquitectónicamente posible);
  3. definidos juntos si forman un conjunto de constantes que de alguna manera están relacionadas;
  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, susceptible de verificación de tipo).

Lo que típicamente distingue las "constantes" aceptables de los "valores mágicos" es una violación de una o más de estas reglas.

Bien usadas, las constantes simplemente nos permiten expresar ciertos axiomas de nuestro código.

Lo que me lleva a un punto final, que un uso excesivo de constantes (y, por lo tanto, un número excesivo de supuestos o restricciones expresados ​​en términos de valores), incluso si cumple con los criterios anteriores (pero especialmente si se desvía de ellos), puede implicar que la solución que se está ideando 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 pueden usarse 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 no ser justificable en términos de ningún principio general, excepto afirmar "¡Soy lo suficientemente mayor como para haber visto este tipo de trabajo, con el que estoy familiarizado, hecho mejor"!

EDITAR: después de haber aceptado una edición, rechazado otra, y ahora haber realizado mi propia edición, ¿puedo considerar ahora el estilo de formato y puntuación de mi lista de reglas para resolver de una vez por todas jaja!

Steve
fuente
2
Me gusta esta respuesta Después de todo "struct" (y cualquier otra palabra reservada) es una cadena mágica para el compilador de C. Hay buenas y malas formas de codificarlos.
Alfred Armstrong
66
Como ejemplo, si alguien ve "X: = 898755167 * Z" en su código, probablemente no sabrán lo que significa, y aún menos probable que sepan que está mal. Pero si ven "Speed_of_Light: constante Integer: = 299792456" alguien lo buscará y sugerirá el valor correcto (y tal vez incluso un mejor tipo de datos).
WGroleau
26
Algunas personas pierden el punto por completo y escriben COMMA = "," en lugar de SEPARATOR = ",". El primero no aclara nada, mientras que el segundo establece el uso previsto y le permite cambiar el separador más adelante en un solo lugar.
marcus
1
@marcus, de hecho! Por supuesto, existe un caso para usar valores literales simples en el lugar; por ejemplo, si un método divide un valor entre dos, puede ser más claro y sencillo simplemente escribir value / 2, en lugar de que value / VALUE_DIVISOReste último se defina como en 2otro lugar. Si pretendía generalizar un método que maneja CSV, probablemente desee que el separador se pase como un parámetro y no se defina como una constante. Pero todo es una cuestión de juicio en el contexto: el ejemplo de @ WGroleau SPEED_OF_LIGHTes algo que querría nombrar explícitamente, pero no todos los literales necesitan esto.
Steve
44
La respuesta superior es mejor que esta respuesta si es necesario convencer que las cuerdas mágicas son "malas". Esta respuesta es mejor si sabe y acepta que son "algo malo" y necesita encontrar la mejor manera de satisfacer las necesidades que atiende de manera sostenible.
corsiKa
34
  • Son difíciles de rastrear.
  • Cambiar todo puede requerir cambiar varios archivos en posiblemente múltiples proyectos (difícil de mantener).
  • A veces es difícil saber cuál es su propósito simplemente mirando su valor.
  • No reutilizar
jason
fuente
44
¿Qué significa "no reutilizar"?
adiós
77
En lugar de crear una variable / constante, etc. y reutilizarla en todo su proyecto / código, está creando una nueva cadena en cada uno que causa una duplicación innecesaria.
jason
Entonces, ¿los puntos 2 y 4 son iguales?
Thomas
44
@ThomasMoors No, él está hablando de la forma en que tienes que construir una nueva cadena cada vez que quieres usar una cadena mágica ya existente , el punto 2 se trata de cambiar la cadena en sí misma
Pierre Arlaud
25

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

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

(tenga en cuenta la cadena mágica "ProductName")

Esto puede conducir 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 detectarán hasta que se ejecute esta línea de código.
  • Cuando alguien decide cambiar el nombre de este campo en el servidor (difícil a la vez que evita la creación de datos, pero no imposible), no puedo buscar fácilmente 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. Entonces 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 escribir "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 cuál es 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 (en oposición a su valor)
  • Los errores tipográficos serán detectados por el compilador. Esto también se aplica cuando se usa un modelo nuevo (posiblemente después de renombrar o eliminar un campo) para regenerar las constantes.

Siguiente en mi lista: esconder estas constantes detrás de clases generadas fuertemente tipadas, entonces también el tipo de datos está asegurado.

Hans Kein
fuente
+1 saca muchos puntos buenos no limitados a la estructura del código: soporte y herramientas IDE, que pueden salvar vidas en grandes proyectos
kmdreko
Si algunas partes de su tipo de entidad son lo suficientemente estáticas como para que valga la pena definir un nombre constante para él, creo que sería más adecuado definir un modelo de datos adecuado para que pueda hacerlo nameField = myEntity.ProductName;.
Lie Ryan
@LieRyan: fue mucho más fácil generar constantes simples y actualizar proyectos existentes para usarlos. Dicho esto, estoy trabajando en generar tipos estáticos para poder hacer precisamente eso
Hans Ke sting
9

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

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

  • La misma cadena aparece varias veces en el código. Esto significa que podría tener un error ortográfico 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 de forma independiente
  • El significado de la cadena no es obvio por 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 "cuerdas mágicas" están bien. Digamos que tiene un analizador simple:

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

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

JacquesB
fuente
77
Creo que su definición de "cadena mágica" es insuficiente, necesita tener algún concepto de ocultar / oscurecer / hacer misterioso. No me referiría al "+" y "-" en ese contraejemplo como "magia", como tampoco me referiría al cero como magia if (dx != 0) { grad = dy/dx; }.
Rupe
2
@Rupe: estoy de acuerdo, pero el OP utiliza la definición " valores de cadena que se especifican directamente dentro del código de la aplicación que tienen un impacto en el comportamiento de la aplicación ", lo que no requiere que la cadena sea misteriosa, por lo que esta es la definición que uso en la respuesta.
JacquesB
77
Con referencia a su ejemplo, he visto declaraciones de cambio que reemplazan "+"y "-"con TOKEN_PLUSy TOKEN_MINUS. ¡Cada vez que lo leía, sentía que era más difícil de leer y depurar por eso! Definitivamente un lugar donde estoy de acuerdo en que usar cadenas simples es mejor.
Cort Ammon
2
Estoy de acuerdo en que hay momentos en que las cuerdas mágicas son apropiadas: evitarlas es una regla general, y todas las reglas generales tienen excepciones. Con suerte, cuando tengamos claro por qué pueden ser algo malo, podremos tomar decisiones inteligentes, en lugar de hacer las cosas porque (1) nunca hemos entendido que puede haber una mejor manera, o (2) nosotros Un desarrollador senior o un estándar de codificación le ha dicho que haga las cosas de manera diferente.
Kramii
2
No sé qué es "magia" aquí. Esos me parecen literales de cadena básicos.
tchrist
6

Para agregar a las respuestas existentes:

Internacionalización (i18n)

Si el texto que se mostrará en la pantalla está codificado y oculto dentro de capas de funciones, tendrá dificultades para proporcionar traducciones de ese texto a otros idiomas.

Algunos entornos de desarrollo (por ejemplo, Qt) manejan las traducciones mediante la búsqueda de una cadena de texto del idioma base al idioma traducido. Las cadenas mágicas generalmente pueden sobrevivir a esto, hasta que decida que desea usar el mismo texto en otro lugar y obtenga un error tipográfico. Incluso entonces, es muy difícil encontrar qué cadenas mágicas deben traducirse cuando desea 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 vuelvan a leer para el entorno local actual mediante la ID única de esa cadena. En este caso, su aplicación con cadenas mágicas simplemente no se puede traducir a otro idioma sin una revisión importante. El desarrollo eficiente requiere que todas las cadenas de texto se ingresen en la base de datos de recursos y se les proporcione una identificación única cuando se escribe el código por primera vez, y posteriormente es relativamente fácil. Intentar rellenar esto después del hecho generalmente requerirá un esfuerzo muy grande (¡y sí, he estado allí!), Así que es mucho mejor hacer las cosas bien en primer lugar.

Graham
fuente
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 lo hacen casi imposible. Una cadena en un lugar se referirá a una clase, método o función en otro lugar, y no existe una manera fácil y automática de determinar que la cadena está acoplada a la clase / método / función simplemente analizando el código. Solo el marco subyacente (angular, p. Ej.) Puede determinar que hay 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 utilizando, más allá del idioma base en el que está codificando.

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

usuario3511585
fuente