O que há de errado com as cordas mágicas?

164

Como desenvolvedor de software experiente, aprendi a evitar seqüências de caracteres mágicas.

Meu problema é que já faz muito tempo desde que eu os uso, esqueci a maioria das razões. Como resultado, estou tendo problemas para explicar por que eles são um problema para meus colegas menos experientes.

Que razões objetivas existem para evitá-las? Que problemas eles causam?

Kramii
fonte
38
O que é uma corda mágica? A mesma coisa que números mágicos ?
LAIV
14
@Laiv: Eles são semelhantes aos números mágicos, sim. Gosto da definição em deviq.com/magic-strings : "As strings mágicas são valores de strings especificados diretamente no código do aplicativo que afetam o comportamento do aplicativo". (A definição de en.wikipedia.org/wiki/Magic_string não é o que eu tenho em mente em tudo)
Kramii
17
isso é engraçado , aprendi a detestar ... mais tarde Que argumentos posso usar para convencer meus juniores ... A história sem fim :-). Eu não tentaria "convencer" que prefiro deixá-los aprender por conta própria. Nada dura mais do que uma lição / idéia alcançada por sua própria experiência. O que você está tentando fazer é doutrinar . Não faça isso a menos que você queira uma equipe de Lemmings.
LAIV
15
@Laiv: Eu adoraria deixar as pessoas aprenderem com sua própria experiência, mas infelizmente isso não é uma opção para mim. Trabalho em um hospital público, onde bugs sutis podem comprometer o atendimento ao paciente e não podemos arcar com custos de manutenção evitáveis.
Kramii
6
@ DavidArno, é exatamente isso que ele está fazendo ao fazer esta pergunta.
User56834

Respostas:

211
  1. Em um idioma que compila, o valor de uma string mágica não é verificado no momento da compilação . Se a sequência precisar corresponder a um padrão específico, você deverá executar o programa para garantir que ele se encaixe nesse padrão. Se você usou algo como uma enumeração, o valor é pelo menos válido no tempo de compilação, mesmo que possa ser o valor errado.

  2. Se uma string mágica estiver sendo escrita em vários lugares, você precisará alterá-las sem nenhuma segurança (como erro em tempo de compilação). Isso pode ser combatido declarando-o apenas em um lugar e reutilizando a variável.

  3. Erros de digitação podem se tornar erros graves. Se você tem uma função:

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

    e alguém digita acidentalmente:

    func("barr");
    

    Isso é pior, quanto mais rara ou complexa for a string, especialmente se você tiver programadores que não estão familiarizados com o idioma nativo do projeto.

  4. Cordas mágicas raramente são auto-documentadas. Se você vir uma string, isso não informa nada sobre o que mais poderia / deveria ser. Você provavelmente terá que examinar a implementação para ter certeza de que escolheu a string certa.

    Esse tipo de implementação está com vazamento , necessitando de documentação externa ou acesso ao código para entender o que deve ser escrito, especialmente porque precisa ser perfeito para caracteres (como no ponto 3).

  5. Com exceção das funções "encontrar cadeia" nos IDEs, há um pequeno número de ferramentas que suportam o padrão.

  6. Por coincidência, você pode usar a mesma seqüência mágica em dois lugares, quando realmente são coisas diferentes; portanto, se você fez um Find & Replace e mudou os dois, um deles pode quebrar enquanto o outro trabalha.

Erdrik Ironrose
fonte
34
Em relação ao primeiro argumento: TypeScript é uma linguagem compilada que pode verificar caracteres literais de string. Isso também invalida o argumento de dois a quatro. Portanto, não as strings são o problema, mas usar um tipo que permita muitos valores. O mesmo raciocínio pode ser aplicado ao uso de números inteiros mágicos para enumerações.
Yogu
11
Como não tenho experiência com o TypeScript, vou adiar seu julgamento lá. O que eu diria então é que seqüências de caracteres não verificadas (como é o caso de todos os idiomas que usei) são o problema.
Erdrik Ironrose
23
O @Yogu Typescript não renomeia todas as suas strings para você se você alterar o tipo literal de string estática que está esperando. Você receberá erros de tempo de compilação para ajudá-lo a encontrar todos eles, mas isso é apenas uma melhoria parcial no 2. Não é nada menos do que absolutamente incrível (porque é isso e eu amo o recurso), mas definitivamente não é completamente eliminar a vantagem de enums. Em nosso projeto, quando usar enums e quando não permanecer permanece um tipo de questão de estilo aberto sobre a qual não temos certeza; ambas as abordagens têm aborrecimentos e vantagens.
KRyan #
30
Uma grande coisa que eu já vi não tanto em strings quanto em números, mas poderia acontecer com strings, é quando você tem dois valores mágicos com o mesmo valor. Então um deles muda. Agora você está passando pelo código alterando o valor antigo para o novo valor, que é trabalho por conta própria, mas também está fazendo um trabalho EXTRA para garantir que não esteja alterando os valores errados. Com variáveis ​​constantes, você não apenas precisa passar por isso manualmente, mas também não se preocupa por ter alterado a coisa errada.
CorsiKa
35
@Yogu Eu diria ainda que, se o valor de um literal de string estiver sendo verificado no momento da compilação, ele deixará de ser um string mágico . Nesse ponto, é apenas um valor constante de const / enum que é escrito de uma maneira engraçada. Dada essa perspectiva, eu argumentaria que seu comentário realmente apóia os pontos de Erdrik, em vez de refutá-los.
GrandOpener 6/18
89

O cume do que as outras respostas entenderam não é que "valores mágicos" sejam ruins, mas que deveriam ser:

  1. definido reconhecidamente como constantes;
  2. definido apenas uma vez em todo o seu domínio de uso (se for arquiteturalmente possível);
  3. definidos juntos se formarem um conjunto de constantes que estão de alguma forma relacionadas;
  4. definido em um nível apropriado de generalidade na aplicação em que são utilizados; e
  5. definidos de forma a limitar seu uso em contextos inadequados (por exemplo, passíveis de verificação de tipo).

O que normalmente distingue "constantes" aceitáveis ​​de "valores mágicos" é alguma violação de uma ou mais dessas regras.

Bem usadas, as constantes simplesmente nos permitem expressar certos axiomas do nosso código.

O que me leva a um ponto final, que o uso excessivo de constantes (e, portanto, um número excessivo de suposições ou restrições expressas em termos de valores), mesmo que de outra forma atenda aos critérios acima (mas principalmente se desviar deles), pode implicar que a solução que está sendo planejada não seja suficientemente geral ou bem estruturada (e, portanto, não estamos mais falando sobre os prós e contras das constantes, mas sobre os prós e contras do código bem estruturado).

Linguagens de alto nível têm construções para padrões em linguagens de nível inferior que teriam que empregar constantes. Os mesmos padrões também podem ser usados ​​na linguagem de nível superior, mas não deveriam.

Mas isso pode ser um julgamento especializado, baseado na impressão de todas as circunstâncias e como deve ser uma solução, e exatamente como esse julgamento será justificado dependerá muito do contexto. De fato, pode não ser justificável em termos de qualquer princípio geral, exceto afirmar "Tenho idade suficiente para já ter visto esse tipo de trabalho, com o qual estou familiarizado, feito melhor"!

EDIT: depois de aceitar uma edição, rejeitar outra e agora ter realizado minha própria edição, agora posso considerar que o estilo de formatação e pontuação da minha lista de regras será resolvido de uma vez por todas haha!

Steve
fonte
2
Eu gosto desta resposta. Afinal, "struct" (e todas as outras palavras reservadas) é uma string mágica para o compilador C. Existem boas e más formas de codificação para eles.
Alfred Armstrong
6
Como exemplo, se alguém vir "X: = 898755167 * Z" no seu código, provavelmente não saberá o que isso significa, e ainda menos provavelmente saberá que está errado. Mas se eles virem "Speed_of_Light: constante Inteiro: = 299792456" alguém procurará e sugerirá o valor correto (e talvez até um tipo de dados melhor).
WGroleau 5/05
26
Algumas pessoas ignoram completamente o ponto e escrevem COMMA = "," em vez de SEPARATOR = ",". O primeiro não esclarece nada, enquanto o último indica o uso pretendido e permite alterar o separador posteriormente em um único local.
Marcus
11
@ Marcus, de fato! É claro que existe um caso para usar valores literais simples no local - por exemplo, se um método divide um valor por dois, pode ser mais claro e mais simples simplesmente escrever value / 2, em vez de value / VALUE_DIVISORcom o último definido como em 2outro lugar. Se você pretendia generalizar um método manipulando CSVs, provavelmente desejaria que o separador fosse passado como um parâmetro e não definido como uma constante. Mas é tudo uma questão de julgamento no contexto - o exemplo de WGroleau SPEED_OF_LIGHTé algo que você gostaria de nomear explicitamente, mas nem todo literal precisa disso.
Steve
4
A resposta principal é melhor que essa resposta, se for necessário convencer que as cordas mágicas são uma "coisa ruim". Essa resposta é melhor se você souber e aceitar que elas são uma "coisa ruim" e precisar encontrar a melhor maneira de atender às necessidades que elas atendem de maneira sustentável.
CorsiKa
34
  • Eles são difíceis de rastrear.
  • Alterar tudo pode exigir a alteração de vários arquivos em possivelmente vários projetos (difíceis de manter).
  • Às vezes, é difícil dizer qual é o objetivo deles apenas olhando seu valor.
  • Sem reutilização.
Jason
fonte
4
O que significa "sem reutilização"?
bye
7
Em vez de criar uma variável / constante etc. e reutilizá-la em todo o seu projeto / código, você está criando uma nova string em cada uma delas, o que causa uma duplicação desnecessária.
Jason
Então os pontos 2 e 4 são iguais?
Thomas
4
@ThomasMoors Não, ele está falando sobre a maneira que você tem que construir uma nova seqüência cada vez que você quiser usar um já existente corda mágica, o ponto 2 é sobre como alterar a seqüência em si
Pierre Arlaud
25

Exemplo da vida real: estou trabalhando com um sistema de terceiros em que "entidades" são armazenadas com "campos". Basicamente, um sistema EAV . Como é bastante fácil adicionar outro campo, você obtém acesso a um usando o nome do campo como string:

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

(observe a sequência mágica "NomeDoProduto")

Isso pode levar a vários problemas:

  • Preciso consultar a documentação externa para saber que "ProductName" existe e sua ortografia exata
  • Além disso, preciso consultar esse documento para ver qual é o tipo de dados desse campo.
  • Erros de digitação nessa seqüência mágica não serão detectados até que essa linha de código seja executada.
  • Quando alguém decide renomear esse campo no servidor (difícil enquanto impede o dataloss, mas não impossível), não consigo pesquisar facilmente meu código para ver onde devo ajustar esse nome.

Portanto, minha solução para isso foi gerar constantes para esses nomes, organizadas por tipo de entidade. Então agora eu posso usar:

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

Ainda é uma constante de seqüência de caracteres e compila exatamente o mesmo binário, mas tem várias vantagens:

  • Depois de digitar "Modelo", meu IDE mostra apenas os tipos de entidade disponíveis, para que eu possa selecionar "Produto" facilmente.
  • Em seguida, meu IDE fornece apenas os nomes de campo disponíveis para esse tipo de entidade, também selecionáveis.
  • A documentação gerada automaticamente mostra qual é o significado desse campo mais o tipo de dados usado para armazenar seus valores.
  • A partir da constante, meu IDE pode encontrar todos os locais em que essa constante exata é usada (em oposição ao seu valor)
  • Erros de digitação serão capturados pelo compilador. Isso também se aplica quando um modelo novo (possivelmente após renomear ou excluir um campo) é usado para regenerar as constantes.

A seguir, na minha lista: oculte essas constantes atrás das classes fortemente tipadas geradas - e também o tipo de dados é protegido.

Hans Keing
fonte
+1 você abrir um monte de bons pontos não se limitando a estrutura de código: suporte IDE e ferramentas, que pode ser um salva-vidas em grandes projetos
kmdreko
Se algumas partes do seu tipo de entidade são estáticas o suficiente para realmente definir um nome constante para ele, acho que seria mais adequado definir apenas um modelo de dados adequado para que você possa fazer isso nameField = myEntity.ProductName;.
Lie Ryan
@LieRyan - era muito mais fácil gerar constantes simples e atualizar projetos existentes para usá-los. Dito isto, estou trabalhando na geração de tipos estáticos, para que eu possa fazer exatamente isso #
311 Hans Hans
9

Cordas mágicas nem sempre são ruins , então esse pode ser o motivo pelo qual você não consegue encontrar uma razão geral para evitá-las. (Por "string mágica", presumo que você queira dizer literal de string como parte de uma expressão e não definida como uma constante.)

Em alguns casos particulares, as cordas mágicas devem ser evitadas:

  • A mesma sequência aparece várias vezes no código. Isso significa que você pode ter um erro de ortografia em um dos lugares. E será um aborrecimento das alterações na string. Transforme a string em uma constante e você evitará esse problema.
  • A cadeia pode mudar independentemente do código em que aparece. Por exemplo. se a string for um texto exibido para o usuário final, provavelmente será alterada independentemente de qualquer alteração lógica. A separação dessa cadeia de caracteres em um módulo separado (ou configuração ou banco de dados externo) facilitará a alteração independente
  • O significado da string não é óbvio no contexto. Nesse caso, a introdução de uma constante tornará o código mais fácil de entender.

Mas em alguns casos, "cordas mágicas" são boas. Digamos que você tenha um analisador simples:

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

Realmente não há mágica aqui, e nenhum dos problemas descritos acima se aplica. Não haveria benefício em definir o IMHO, string Plus="+"etc. Mantenha-o simples.

JacquesB
fonte
7
Eu acho que sua definição de "corda mágica" é insuficiente, ela precisa ter algum conceito de ocultar / obscurecer / tornar misterioso. Eu não me referiria ao "+" e "-" nesse contra-exemplo como "mágica", assim como não me referiria ao zero como mágica if (dx != 0) { grad = dy/dx; }.
Rupe
2
@Rupe: Eu concordo, mas o OP usa a definição " valores de string que são especificados diretamente no código do aplicativo que afetam o comportamento do aplicativo. ", Que não exigem que a string seja misteriosa; a resposta.
precisa saber é o seguinte
7
Com referência ao seu exemplo, eu vi instruções switch que substituíram "+"e "-"por TOKEN_PLUSe TOKEN_MINUS. Toda vez que eu li, parecia que era mais difícil ler e depurar por causa disso! Definitivamente, um lugar em que concordo que usar cordas simples é melhor.
Cort Ammon
2
Concordo que há momentos em que as cordas mágicas são apropriadas: evitá-las é uma regra prática e todas as regras práticas têm exceções. Felizmente, quando tivermos certeza de por que eles podem ser uma coisa ruim, seremos capazes de fazer escolhas inteligentes, em vez de fazer coisas porque (1) nunca entendemos que pode haver uma maneira melhor ou (2) foi instruído a fazer as coisas de maneira diferente por um desenvolvedor sênior ou por um padrão de codificação.
Kramii
2
Eu não sei o que é "mágica" aqui. Aqueles parecem literais básicos para mim.
tchrist
6

Para adicionar às respostas existentes:

Internacionalização (i18n)

Se o texto a ser exibido na tela estiver codificado e oculto em camadas de funções, será muito difícil fornecer traduções desse texto para outros idiomas.

Alguns ambientes de desenvolvimento (por exemplo, Qt) manipulam traduções pesquisando de uma sequência de texto do idioma base para o idioma traduzido. Geralmente, as cordas mágicas podem sobreviver a isso - até você decidir que deseja usar o mesmo texto em outro lugar e obter um erro de digitação. Mesmo assim, é muito difícil encontrar quais seqüências mágicas precisam ser traduzidas quando você deseja adicionar suporte para outro idioma.

Alguns ambientes de desenvolvimento (por exemplo, MS Visual Studio) adotam outra abordagem e exigem que todas as seqüências traduzidas sejam mantidas em um banco de dados de recursos e lidas novamente para a localidade atual pelo ID exclusivo dessa sequência. Nesse caso, seu aplicativo com seqüências de caracteres mágicas simplesmente não pode ser traduzido para outro idioma sem grandes retrabalhos. O desenvolvimento eficiente exige que todas as seqüências de texto sejam inseridas no banco de dados de recursos e receba um ID exclusivo quando o código for escrito pela primeira vez, e o i18n a partir de então é relativamente fácil. Tentar preencher isso após o fato normalmente exige um esforço muito grande (e sim, eu já estive lá!), Portanto é muito melhor fazer as coisas corretamente em primeiro lugar.

Graham
fonte
3

Isso não é uma prioridade para todos, mas se você quiser calcular métricas de acoplamento / coesão no seu código de maneira automatizada, as seqüências mágicas tornam isso quase impossível. Uma string em um local se refere a uma classe, método ou função em outro local, e não há uma maneira fácil e automática de determinar que a string seja acoplada à classe / método / função apenas analisando o código. Somente a estrutura subjacente (Angular, por exemplo) pode determinar que há uma ligação - e só pode fazê-lo em tempo de execução. Para obter as informações de acoplamento, seu analisador precisaria saber tudo sobre a estrutura que estava usando, acima e além do idioma base em que está codificando.

Mas, novamente, isso não é algo com o qual muitos desenvolvedores se preocupam.

user3511585
fonte