O que é um número mágico?
Por que deveria ser evitado?
Existem casos em que é apropriado?
O que é um número mágico?
Por que deveria ser evitado?
Existem casos em que é apropriado?
Respostas:
Um número mágico é um uso direto de um número no código.
Por exemplo, se você tiver (em Java):
public class Foo {
public void setPassword(String password) {
// don't do this
if (password.length() > 7) {
throw new InvalidArgumentException("password");
}
}
}
Isso deve ser refatorado para:
public class Foo {
public static final int MAX_PASSWORD_SIZE = 7;
public void setPassword(String password) {
if (password.length() > MAX_PASSWORD_SIZE) {
throw new InvalidArgumentException("password");
}
}
}
Melhora a legibilidade do código e é mais fácil de manter. Imagine o caso em que eu defino o tamanho do campo da senha na GUI. Se eu usar um número mágico, sempre que o tamanho máximo mudar, tenho que mudar em dois locais de código. Se eu esquecer uma, isso levará a inconsistências.
O JDK está cheia de exemplos como em Integer
, Character
e Math
classes.
PS: Ferramentas de análise estática como FindBugs e PMD detectam o uso de números mágicos no seu código e sugerem a refatoração.
TRUE
/ FALSE
)
Um número mágico é um valor codificado que pode ser alterado posteriormente, mas pode ser difícil de atualizar.
Por exemplo, digamos que você tenha uma página que exibe os últimos 50 pedidos em uma página de visão geral "Seus pedidos". 50 é o número mágico aqui, porque não é definido por padrão ou convenção, é um número que você criou pelas razões descritas nas especificações.
Agora, o que você faz é ter os 50 em lugares diferentes - seu script SQL ( SELECT TOP 50 * FROM orders
), seu site (seus últimos 50 pedidos), seu login de pedido ( for (i = 0; i < 50; i++)
) e, possivelmente, muitos outros lugares.
Agora, o que acontece quando alguém decide mudar de 50 para 25? ou 75? ou 153? Agora você precisa substituir os 50 em todos os lugares e provavelmente perderá. Localizar / Substituir pode não funcionar, porque 50 pode ser usado para outras coisas, e a substituição cega de 50 por 25 pode ter outros efeitos colaterais ruins (por exemplo, sua Session.Timeout = 50
chamada, que também é definida como 25 e os usuários começam a relatar intervalos muito frequentes).
Além disso, o código pode ser difícil de entender, ou seja, " if a < 50 then bla
" - se você achar que, no meio de uma função complicada, outros desenvolvedores que não estão familiarizados com o código poderão se perguntar "WTF is 50 ???"
É por isso que é melhor ter números tão ambíguos e arbitrários em exatamente 1 lugar - " const int NumOrdersToDisplay = 50
", porque isso torna o código mais legível (" if a < NumOrdersToDisplay
", também significa que você só precisa alterá-lo em 1 local bem definido.
Locais onde os números mágicos são apropriados são tudo o que é definido através de um padrão, SmtpClient.DefaultPort = 25
ou seja, TCPPacketSize = whatever
( ou não, se isso é padronizado). Além disso, tudo o que é definido apenas em 1 função pode ser aceitável, mas isso depende do contexto.
SmtpClient.DefaultPort = 25
é possivelmente clara er do que SmtpClient.DefaultPort = DEFAULT_SMTP_PORT
.
25
todo o aplicativo e certificar-se de alterar apenas as ocorrências 25
referentes à porta SMTP, e não os 25 anos, por exemplo, a largura de uma coluna da tabela ou o número de registros para mostrar em uma página.
IANA
.
Você deu uma olhada na entrada da Wikipedia para número mágico?
Ele entra em detalhes sobre todas as maneiras pelas quais a referência do número mágico é feita. Aqui está uma citação sobre número mágico como uma prática de programação ruim
O termo número mágico também se refere à má prática de programação de usar números diretamente no código fonte, sem explicação. Na maioria dos casos, isso torna os programas mais difíceis de ler, entender e manter. Embora a maioria dos guias faça uma exceção para os números zero e um, é uma boa ideia definir todos os outros números no código como constantes nomeadas.
Magia: semântica desconhecida
Constante simbólica -> Fornece o contexto semântico e correto corretos para uso
Semântica: O significado ou propósito de uma coisa.
"Crie uma constante, nomeie-a após o significado e substitua o número por ela." - Martin Fowler
Primeiro, números mágicos não são apenas números. Qualquer valor básico pode ser "mágico". Os valores básicos são entidades manifestas, como números inteiros, reais, duplos, flutuantes, datas, seqüências de caracteres, booleanos, caracteres e assim por diante. O problema não é o tipo de dados, mas o aspecto "mágico" do valor, conforme aparece no texto do código.
O que queremos dizer com "mágica"? Para ser mais preciso: por "mágica", pretendemos apontar para a semântica (significado ou propósito) do valor no contexto do nosso código; que é desconhecido, incognoscível, incerto ou confuso. Esta é a noção de "mágica". Um valor básico não é mágico quando o seu significado semântico ou propósito de ser lá é rápida e facilmente conhecido, claro e compreendido (sem confundir) a partir do contexto circundante sem palavras auxiliares especiais (por exemplo, constante simbólica).
Portanto, identificamos números mágicos medindo a capacidade de um leitor de código de conhecer, ser claro e entender o significado e a finalidade de um valor básico a partir de seu contexto circundante. Quanto menos conhecido, menos claro e mais confuso for o leitor, mais "mágico" será o valor básico.
Temos dois cenários para nossos valores básicos mágicos. Somente o segundo é de importância primordial para programadores e código:
Uma dependência abrangente de "mágica" é como o valor básico único (por exemplo, número) não possui semântica comumente conhecida (como Pi), mas possui uma semântica conhecida localmente (por exemplo, seu programa), que não é totalmente clara do contexto ou pode ser abusada em bons ou maus contextos.
A semântica da maioria das linguagens de programação não nos permitirá usar valores básicos isolados, exceto (talvez) como dados (ou seja, tabelas de dados). Quando encontramos "números mágicos", geralmente fazemos isso em um contexto. Portanto, a resposta para
"Eu substituo esse número mágico por uma constante simbólica?"
é:
"Com que rapidez você pode avaliar e entender o significado semântico do número (sua finalidade por estar lá) em seu contexto?"
Com esse pensamento em mente, podemos ver rapidamente como um número como Pi (3,14159) não é um "número mágico" quando colocado no contexto apropriado (por exemplo, 2 x 3,14159 x raio ou 2 * Pi * r). Aqui, o número 3.14159 é Pi mentalmente reconhecido sem o identificador constante simbólico.
Ainda assim, geralmente substituímos 3.14159 por um identificador constante simbólico como Pi, devido ao comprimento e complexidade do número. Os aspectos de comprimento e complexidade do Pi (juntamente com a necessidade de precisão) geralmente significam que o identificador simbólico ou constante é menos propenso a erros. O reconhecimento de "Pi" como um nome é simplesmente um bônus conveniente, mas não é o principal motivo para ter a constante.
Deixando de lado constantes comuns como Pi, vamos nos concentrar principalmente em números com significados especiais, mas cujos significados são restritos ao universo do nosso sistema de software. Esse número pode ser "2" (como um valor inteiro básico).
Se eu usar o número 2 por si só, minha primeira pergunta poderá ser: O que significa "2"? O significado de "2" por si só é desconhecido e incognoscível sem contexto, deixando seu uso pouco claro e confuso. Mesmo tendo apenas "2" em nosso software não acontecerá por causa da semântica da linguagem, queremos ver que "2" por si só não possui semântica especial ou propósito óbvio de estar sozinho.
Vamos colocar nosso "2" único em um contexto de:, padding := 2
onde o contexto é um "Contêiner da GUI". Nesse contexto, o significado de 2 (como pixels ou outra unidade gráfica) oferece uma rápida suposição de sua semântica (significado e finalidade). Podemos parar por aqui e dizer que 2 está bem neste contexto e não há mais nada que precisamos saber. No entanto, talvez no nosso universo de software essa não seja a história toda. Há mais do que isso, mas "padding = 2" como contexto não pode revelá-lo.
Vamos fingir ainda que 2 como preenchimento de pixel em nosso programa é da variedade "default_padding" em todo o sistema. Portanto, escrever a instrução padding = 2
não é bom o suficiente. A noção de "padrão" não é revelada. Somente quando escrevo: padding = default_padding
como um contexto e depois para outro lugar: default_padding = 2
percebo plenamente um significado melhor e mais completo (semântico e objetivo) de 2 em nosso sistema.
O exemplo acima é muito bom porque "2" por si só pode ser qualquer coisa. Somente quando limitamos o alcance e o domínio de entendimento a "meu programa", em que 2 é a parte default_padding
da GUI UX de "meu programa", é que finalmente entendemos "2" em seu contexto apropriado. Aqui "2" é um número "mágico", que é fatorado para uma constante simbólica default_padding
no contexto da GUI UX de "meu programa" para fazer com que seja usado o mais default_padding
rapidamente compreendido no contexto maior do código anexo.
Assim, qualquer valor básico, cujo significado (semântico e objetivo) não possa ser compreendido suficiente e rapidamente, é um bom candidato a uma constante simbólica no lugar do valor básico (por exemplo, número mágico).
Os números em uma escala também podem ter semântica. Por exemplo, finja que estamos fazendo um jogo de D&D, onde temos a noção de um monstro. Nosso objeto monstro tem um recurso chamado life_force
, que é um número inteiro. Os números têm significados desconhecidos ou claros sem palavras para fornecer significado. Assim, começamos dizendo arbitrariamente:
A partir das constantes simbólicas acima, começamos a ter uma imagem mental da vitalidade, morte e "mortos-vivos" (e possíveis ramificações ou consequências) para nossos monstros em nosso jogo de D&D. Sem essas palavras (constantes simbólicas), ficamos apenas com os números que variam de -10 .. 10
. Apenas o intervalo sem as palavras nos deixa em um local possivelmente de grande confusão e possivelmente com erros em nosso jogo, se diferentes partes do jogo dependem do que esse intervalo de números significa para várias operações, como attack_elves
ou seek_magic_healing_potion
.
Portanto, ao procurar e considerar a substituição de "números mágicos", queremos fazer perguntas muito específicas sobre os números no contexto do nosso software e até como os números interagem semanticamente entre si.
Vamos revisar quais perguntas devemos fazer:
Você pode ter um número mágico se ...
Examine os valores básicos constantes constantes do manifesto autônomo no texto do código. Faça cada pergunta lenta e cuidadosamente sobre cada instância desse valor. Considere a força da sua resposta. Muitas vezes, a resposta não é em preto e branco, mas apresenta tons de significado e propósito incompreendidos, velocidade de aprendizado e velocidade de compreensão. Também é necessário ver como ele se conecta à máquina de software ao seu redor.
No final, a resposta para a substituição é responder à medida (em sua mente) da força ou fraqueza do leitor para fazer a conexão (por exemplo, "entenda"). Quanto mais rapidamente eles entendem o significado e o propósito, menos "mágica" você tem.
CONCLUSÃO: Substitua os valores básicos por constantes simbólicas apenas quando a mágica for grande o suficiente para causar bugs difíceis de detectar decorrentes de confusões.
Um número mágico é uma sequência de caracteres no início de um formato de arquivo ou troca de protocolo. Esse número serve como uma verificação de sanidade.
Exemplo: Abra qualquer arquivo GIF, você verá no início: GIF89. "GIF89" é o número mágico.
Outros programas podem ler os primeiros caracteres de um arquivo e identificar corretamente os GIFs.
O perigo é que dados binários aleatórios possam conter esses mesmos caracteres. Mas é muito improvável.
Quanto à troca de protocolos, você pode usá-lo para identificar rapidamente que a 'mensagem' atual que está sendo passada a você está corrompida ou não é válida.
Números mágicos ainda são úteis.
Na programação, um "número mágico" é um valor que deve receber um nome simbólico, mas foi inserido no código como um literal, geralmente em mais de um local.
É ruim pela mesma razão que o SPOT (Ponto Único da Verdade) é bom: se você quisesse mudar essa constante posteriormente, teria que procurar no seu código para encontrar todas as instâncias. Também é ruim porque pode não estar claro para outros programadores o que esse número representa, daí a "mágica".
Às vezes, as pessoas levam mais longe a eliminação do número mágico, movendo essas constantes em arquivos separados para atuar como configuração. Às vezes, isso é útil, mas também pode criar mais complexidade do que vale a pena.
(foo[i]+foo[i+1]+foo[i+2]+1)/3
pode ser avaliada muito mais rápido que um loop. Se alguém substituísse o 3
arquivo sem reescrever o código como um loop, alguém que visse ITEMS_TO_AVERAGE
definido como 3
poderia descobrir que poderia alterá-lo 5
e fazer com que o código fizesse a média de mais itens. Por outro lado, alguém que olhasse para a expressão com o literal 3
perceberia que o 3
representa o número de itens sendo somados.
Um número mágico também pode ser um número com semântica especial codificada. Por exemplo, vi um sistema em que os IDs de registro> 0 eram tratados normalmente, 0 era "novo registro", -1 era "essa é a raiz" e -99 era "isso foi criado na raiz". 0 e -99 levariam o WebService a fornecer um novo ID.
O que é ruim nisso é que você está reutilizando um espaço (o de números inteiros assinados para IDs de registro) para habilidades especiais. Talvez você nunca deseje criar um registro com o ID 0, ou com um ID negativo, mas mesmo se não, todas as pessoas que olharem para o código ou para o banco de dados poderão tropeçar nisso e ficar confusas no início. Escusado será dizer que esses valores especiais não estavam bem documentados.
Indiscutivelmente, 22, 7, -12 e 620 também contam como números mágicos. ;-)
Um problema que não foi mencionado com o uso de números mágicos ...
Se você tiver muitos deles, as chances são razoavelmente boas de você ter dois propósitos diferentes para os quais está usando números mágicos, onde os valores são os mesmos.
E então, com certeza, você precisa alterar o valor ... apenas para um propósito.
Suponho que seja uma resposta à minha resposta à sua pergunta anterior. Na programação, um número mágico é uma constante numérica incorporada que aparece sem explicação. Se aparecer em dois locais distintos, poderá levar a circunstâncias em que uma instância é alterada e não outra. Por ambos os motivos, é importante isolar e definir as constantes numéricas fora dos locais onde são usadas.
Eu sempre usei o termo "número mágico" de maneira diferente, como um valor obscuro armazenado em uma estrutura de dados que pode ser verificada como uma verificação rápida de validade. Por exemplo, os arquivos gzip contêm 0x1f8b08 como seus três primeiros bytes, os arquivos de classe Java começam com 0xcafebabe etc.
Você costuma ver números mágicos incorporados nos formatos de arquivo, porque os arquivos podem ser enviados de maneira bastante promíscua e perdem quaisquer metadados sobre como eles foram criados. No entanto, números mágicos também são às vezes usados para estruturas de dados na memória, como chamadas ioctl ().
Uma verificação rápida do número mágico antes de processar o arquivo ou a estrutura de dados permite sinalizar erros com antecedência, em vez de percorrer todo o processo potencialmente demorado para anunciar que a entrada foi completa.
Vale a pena notar que às vezes você deseja números "codificados" não configuráveis no seu código. Há vários famosos, incluindo 0x5F3759DF, que é usado no algoritmo de raiz quadrada inversa otimizada.
Nos raros casos em que encontro a necessidade de usar esses Números Mágicos, eu os defino como uma const no meu código e documento por que eles são usados, como funcionam e de onde vieram.
Que tal inicializar uma variável na parte superior da classe com um valor padrão? Por exemplo:
public class SomeClass {
private int maxRows = 15000;
...
// Inside another method
for (int i = 0; i < maxRows; i++) {
// Do something
}
public void setMaxRows(int maxRows) {
this.maxRows = maxRows;
}
public int getMaxRows() {
return this.maxRows;
}
Nesse caso, 15000 é um número mágico (de acordo com o CheckStyles). Para mim, definir um valor padrão é bom. Eu não quero ter que fazer:
private static final int DEFAULT_MAX_ROWS = 15000;
private int maxRows = DEFAULT_MAX_ROWS;
Isso dificulta a leitura? Eu nunca considerei isso até instalar o CheckStyles.
static final
constantes são um exagero quando você as usa em um método. Uma final
variável declarada na parte superior do método é IMHO mais legível.
@ eed3si9n: Eu até sugeriria que '1' é um número mágico. :-)
Um princípio relacionado aos números mágicos é que todos os fatos tratados pelo seu código devem ser declarados exatamente uma vez. Se você usar números mágicos em seu código (como o exemplo de tamanho de senha que @marcio forneceu, você poderá facilmente duplicar esse fato e, quando sua compreensão desse fato mudar, você terá um problema de manutenção.
factorial n = if n == BASE_CASE then BASE_VALUE else n * factorial (n - RECURSION_INPUT_CHANGE); RECURSION_INPUT_CHANGE = 1; BASE_CASE = 0; BASE_VALUE = 1
E as variáveis de retorno?
Acho especialmente desafiador ao implementar procedimentos armazenados .
Imagine o próximo procedimento armazenado (sintaxe errada, eu sei, apenas para mostrar um exemplo):
int procGetIdCompanyByName(string companyName);
Ele retorna o ID da empresa, se existir em uma tabela específica. Caso contrário, ele retornará -1. De alguma forma, é um número mágico. Algumas das recomendações que li até agora afirmam que realmente preciso criar algo assim:
int procGetIdCompanyByName(string companyName, bool existsCompany);
A propósito, o que deve retornar se a empresa não existir? Ok: ele definirá existesCompany como false , mas também retornará -1.
Outra opção é fazer duas funções separadas:
bool procCompanyExists(string companyName);
int procGetIdCompanyByName(string companyName);
Portanto, uma pré-condição para o segundo procedimento armazenado é que a empresa exista.
Mas tenho medo de simultaneidade, porque nesse sistema, uma empresa pode ser criada por outro usuário.
A linha inferior, a propósito, é: o que você acha de usar esse tipo de "números mágicos" que são relativamente conhecidos e seguros para dizer que algo é malsucedido ou que algo não existe?
Outra vantagem de extrair um número mágico como uma constante oferece a possibilidade de documentar claramente as informações da empresa.
public class Foo {
/**
* Max age in year to get child rate for airline tickets
*
* The value of the constant is {@value}
*/
public static final int MAX_AGE_FOR_CHILD_RATE = 2;
public void computeRate() {
if (person.getAge() < MAX_AGE_FOR_CHILD_RATE) {
applyChildRate();
}
}
}
const myNum = 22; const number = myNum / 11;
agora meus 11 podem ser pessoas ou garrafas de cerveja ou algo assim, então eu mudaria 11 para uma constante como habitantes.