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

155

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

Meu problema é que faz tanto tempo desde que os usei, que esqueci a maioria dos motivos. Como resultado, estou com dificuldade 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?

    
por Kramii 05.02.2018 / 10:29
fonte

7 respostas

203
  1. Em uma linguagem que compila, o valor de uma string mágica é não verificado em tempo de compilação . Se a cadeia de caracteres deve corresponder a um padrão específico, você deve executar o programa para garantir que ele se encaixa nesse padrão. Se você usou algo como um enum, o valor é pelo menos válido em tempo de compilação, mesmo que possa ser o valor errado.

  2. Se uma string mágica está sendo escrita em vários lugares você tem que mudar todos eles sem qualquer segurança (como erro em tempo de compilação). Isso pode ser combatido apenas declarando-o em um lugar e reutilizando a variável.

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

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

    e alguém acidentalmente digita:

    func("barr");
    

    Isso é pior quanto mais raro ou complexo for a string, especialmente se você tiver programadores que não estejam familiarizados com a linguagem nativa do projeto.

  4. As cordas mágicas raramente são autodocumentadas. Se você vir uma string, isso não lhe diz nada do que mais a string poderia / deveria ser. Você provavelmente terá que investigar a implementação para ter certeza de que escolheu a string correta.

    Esse tipo de implementação é leaky , precisando de documentação externa ou acesso ao código para entender o que deve ser escrito, especialmente porque ele precisa ser perfeito (como no ponto 3).

  5. Além das funções "localizar string" nos IDEs, há um pequeno número de ferramentas que suportam o padrão.

  6. Você pode coincidentemente usar a mesma string mágica em dois lugares, quando na verdade eles são coisas diferentes, por isso, se você fez um Find & Substituir e mudou ambos, um deles poderia quebrar enquanto o outro funcionava.

por 05.02.2018 / 11:10
fonte
88

A cimeira do que as outras respostas captaram, não é que os "valores mágicos" são ruins, mas que deveriam ser:

  1. definido reconhecidamente como constantes;
  2. definidos apenas uma vez em todo o domínio de uso (se possível em arquitetura);
  3. definidos em conjunto se formam um conjunto de constantes de alguma forma relacionadas;
  4. definidos em um nível apropriado de generalidade na aplicação em que são usados; e
  5. definido de maneira a limitar seu uso em contextos inapropriados (por exemplo, passível de verificação de tipos).

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

Usadas bem, as constantes simplesmente nos permitem expressar certos axiomas do nosso código.

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

Linguagens de alto nível têm construções de 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 devem ser.

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

EDITAR: tendo aceitado uma edição, rejeitado outra e tendo agora realizado minha própria edição, posso agora considerar o estilo de formatação e pontuação da minha lista de regras a serem resolvidas de uma vez por todas haha !

    
por 05.02.2018 / 14:40
fonte
33
  • Eles são difíceis de acompanhar.
  • 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 propósito deles, apenas olhando para o valor deles.
  • Não reutilizar.
por 05.02.2018 / 10:41
fonte
24

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 string mágica "ProductName")

Isso pode levar a vários problemas:

  • Preciso consultar a documentação externa para saber que o "ProductName" existe e sua ortografia exata
  • Além disso, preciso consultar esse documento para ver qual é o tipo de dados desse campo.
  • Os erros de digitação nesta string mágica não serão capturados até que esta linha de código seja executada.
  • Quando alguém decide renomear esse campo no servidor (difícil ao evitar dataloss, mas não impossível), não consigo pesquisar com facilidade pelo meu código para ver onde devo ajustar esse nome.

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

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

Ainda é uma constante de string e compila exatamente o mesmo binário, mas tem várias vantagens:

  • Depois de digitar "Modelo", meu IDE mostra apenas os tipos de entidades 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 e o tipo de dados usado para armazenar seus valores.
  • A partir da constante, meu IDE pode encontrar todos os locais onde essa constante exata é usada (em oposição ao seu valor)
  • Os 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.

Em seguida, na minha lista: oculte estas constantes atrás das classes strongmente tipadas - então também o tipo de dados é seguro.

    
por 05.02.2018 / 17:21
fonte
9

As strings mágicas nem sempre são ruins , portanto, isso pode ser o motivo pelo qual você não pode inventar um motivo básico para evitá-las. (Por "string mágica" eu assumo que você quer dizer string literal como parte de uma expressão, e não definida como uma constante.)

Em alguns casos particulares, as sequências de caracteres mágicas devem ser evitadas:

  • A mesma string aparece várias vezes no código. Isso significa que você pode ter um erro de ortografia em um dos lugares. E será um incômodo das mudanças de string. Transforme a string em uma constante e você evitará esse problema.
  • A string pode ser alterada independentemente do código em que aparece. Por exemplo. se a string for texto exibido para o usuário final, ela provavelmente será alterada independentemente de qualquer alteração lógica. Separar essa cadeia em um módulo separado (ou configuração externa ou banco de dados) facilitará a mudança 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, "strings 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 nenhum benefício para a IMHO definir string Plus="+" etc. Mantenha-a simples.

    
por 05.02.2018 / 18:15
fonte
5

Para adicionar as respostas existentes:

Internacionalização (i18n)

Se o texto a ser exibido na tela estiver codificado e enterrado em camadas de funções, você terá muita dificuldade em fornecer traduções desse texto para outros idiomas.

Alguns ambientes de desenvolvimento (por exemplo, Qt) lidam com traduções por pesquisa a partir de uma cadeia de texto do idioma base para o idioma traduzido. Geralmente, as cordas mágicas podem sobreviver até que você decida usar o mesmo texto em outro lugar e receber um erro de digitação. Mesmo assim, é muito difícil descobrir quais sequências de caracteres mágicas precisam ser traduzidas quando você deseja adicionar suporte a outro idioma.

Alguns ambientes de desenvolvimento (por exemplo, o MS Visual Studio) adotam outra abordagem e exigem que todas as cadeias de caracteres traduzidas sejam mantidas em um banco de dados de recursos e lidas de volta para a localidade atual pelo ID exclusivo dessa cadeia. Nesse caso, seu aplicativo com seqüências de caracteres mágicas simplesmente não pode ser traduzido para outro idioma sem um grande retrabalho. O desenvolvimento eficiente exige que todas as cadeias de texto sejam inseridas no banco de dados de recursos e recebam uma identificação exclusiva quando o código é escrito pela primeira vez, e a partir daí é relativamente fácil. Tentar preencher isso após o fato normalmente exigirá um esforço muito grande (e sim, eu já estive lá!), Então é muito melhor fazer as coisas da maneira certa.

    
por 09.02.2018 / 15:58
fonte
3

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

Mas, novamente, isso não é algo que muitos desenvolvedores se importem.

    
por 06.02.2018 / 16:05
fonte