Todo número no código é considerado um "número mágico"?

21

Então, todo número no código que estamos enviando para um método como argumento é considerado um Número Mágico? Para mim, não deveria. Eu acho que se algum número é digamos que é para um tamanho mínimo de nome de usuário e começamos a usar "6" no código ... então sim, temos um problema de manutenção e aqui "6" é um número mágico ... mas se estamos chamando um método que um de seus argumentos aceita um número inteiro, por exemplo, como o i-ésimo membro de uma coleção e depois passamos "0" para essa chamada de método, nesse caso, não vejo esse "0" como uma mágica número. O que você acha?

Blake
fonte
4
No seu exemplo, o que o 0 representa?
Aaron Kurtzhals
2
No caso que você ilustra, esse "0" não possui propriedades mágicas.
Tulains Córdova
4
Tudo, exceto 0,1 e 42 é mágica
MAWG

Respostas:

43

Se o significado do número é muito claro no contexto, não acho que seja um problema de "número mágico".

Exemplo: digamos que você esteja tentando obter a substring de uma string, desde o início até algum token, e o código se parece com isso (linguagem e biblioteca imaginárias):

s := substring(big_string, 0, findFirstOccurence(SOME_TOKEN, big_string));

Nesse contexto, o significado do número 0 é claro o suficiente. Suponho que você possa definir START_OF_SUBSTRINGe defini-lo como 0, mas, neste caso, acho que seria um exagero (embora seja a abordagem correta se você soubesse que o início de sua substring pode não ser 0, mas isso depende das especificidades de sua situação).

Outro exemplo pode ser se você estiver tentando determinar se um número é par ou ímpar. Escrita:

isEven := x % 2;

não é tão estranho quanto:

TWO := 2;
isEven := x % TWO;

Testando números negativos como

MINUS_ONE := -1;
isNegativeInt := i <= MINUS_ONE;

também me parece estranho, eu prefiro ver

isNegativeInt := i <= -1;
FrustratedWithFormsDesigner
fonte
6
Para dar outro exemplo, no código em que você trabalha explicitamente com graus em um círculo, seria justo usar um número como 360para marcar uma rotação completa com o entendimento de que a maioria das pessoas saberá o que isso significa (embora isso é um caso em que não seria ferido para fornecer uma constante)
KChaloux
11
KChaloux: Se eu pudesse, faria o seu comentário com -1. 360 é um número mágico. Se 360 ​​for um valor para outra constante, você terá 2 conjuntos para 360 que não são relacionados e são indistinguíveis. Junior aparece, diz "Esse é um número mágico", pesquisa global e substitui 360 por "Degrees_in_Circle", executa todos os testes de unidade e regressão, todos passam - entregam correção de código. Código agora um cães de pequeno-almoço, e todos nós sabemos o que acontece com que depois de um curto período de tempo .......
mattnz
4
@mattnz: Espero que esse tipo de alteração de código em grande escala seja rapidamente capturada (espero que durante a revisão do código, se eles são tão jovens) muito antes de entrar em produção. Eu acho que alguém que faria isso nesse contexto provavelmente também substituiria 0no contexto do meu exemplo de substring. Nesse caso, essa pode ser a menor quantidade de dano que eles podem causar. Faz muito tempo desde que eu fiz qualquer codificação que fizesse cálculos geométricos, mas geralmente os valores 15, 30, 45, 60, 90, 180, 360 eram constantes que foram aceitas. Eu nunca vi ninguém definir FIFTEEN_DEGREES, ...
FrustratedWithFormsDesigner
5
@KChaloux O exemplo pode realmente desmoronar se houver uma mudança de Graus para Radianos. Em 360, você está expressando 1 rotação completa. Como existem várias representações para o mesmo valor, ele deve ser retirado. Especialmente considerando 360PI poderia ter a mesma aparência de 2PI (180 rotações, mas ainda apontando a mesma direção no final) ou 360 rotações igual a 1 rotação, mas os efeitos colaterais podem ser diferentes.
Chris
14
Um pouco parecido com isso, TWO e MINUS_ONE são totalmente ruins, porque substituir um número mágico por sua renderização em texto é idiota. O nome da constante deve transmitir seu significado. Exceto que seus exemplos são sobre fatos fundamentais sobre números, intimamente ligados a esses números específicos, então não há realmente nenhum significado além disso.
Michael Borgwardt
17
bool hasApples = apples > 0;

É óbvio que zero significa ausência. Acho 0 mais fácil de entender do que uma variável chamada "ausênciaValor".


for(int i=0; i < arr.length; i++)

É óbvio que 0 é a posição inicial. Eu ficaria confuso com uma variável chamada "firstPosition". Essa variável me faria pensar se a posição inicial poderia mudar.

mike30
fonte
14

Sugiro três fatores principais para decidir se algo deve ser uma declaração constante:

  1. O número é algo que é precisa e concisamente representável
  2. Existem cenários plausíveis sob os quais o valor precisaria mudar, mas o código não precisaria ser reescrito
  3. Alguém que vê o número está apto a reconhecê-lo mais rapidamente ou menos rapidamente do que alguém que vê uma constante nomeada

Algo como pi provavelmente deve ser escrito como uma constante nomeada, e não como um literal numérico, uma vez que um literal numérico pode ser desnecessariamente detalhado, impreciso ou ambos. Algo como o número de slots em um cache provavelmente deve ser uma constante nomeada (embora veja a nota abaixo) para permitir a possibilidade de expandir o cache sem precisar modificar todo o código que o usa. Coisas como os números "4", "28" e "29" na declaração if ((year % 4)==0) FebruaryDays = 29; else FebruaryDays = 28;provavelmente não devem ser nomeadas constantes, pois a expressão é quase certamente mais legível do que if ((year % YearsBetweenLeapYears)==0) FebruaryDays = FebruaryDaysInLeapYear; else FebruaryDays = FebruaryDaysInNonLeapYear;. Observe que os mantenedores de padrões indicaram que a duração de fevereiro de 2100 naquele ano não corresponderá à fórmula acima, impedimento de lidar corretamente com essas datas (ou seja, o código não será acionado por excesso de número inteiro ou outros problemas desse tipo).

Uma ressalva importante da regra 2 é que, em alguns casos, o código pode depender de números codificados de maneira que não possa ser prontamente representada por uma constante nomeada. Por exemplo, um método que calcula um produto cruzado de dois vetores passados ​​como parâmetros discretos somente terá significado quando usado em vetores tridimensionais. O número necessário de dimensões não é um valor que possa ser alterado significativamente sem reescrever completamente a rotina. Mesmo que se previsse uma possível necessidade de calcular o produto cruzado de três vetores quadridimensionais, o uso de uma constante nomeada para o valor "3" faria pouco para facilitar a satisfação dessa necessidade.

supercat
fonte
4

Como todos os princípios, isso é uma questão de grau. De um modo geral, os literais numéricos no código-fonte são mais suspeitos quanto maiores. Um comprimento máximo como 10 ou um endereço de memória como 0x587FB0 é obviamente uma prática ruim - é quase certo que mais cedo ou mais tarde você precisará repetir esses valores mais de uma vez, criando um risco de incompatibilidade e erros sutis introduzidos em locais que não eram mudou.

0 está no outro extremo da escala; ainda é suspeito, mas não tanto. Você está usando 0 como um valor sentinela? Então você provavelmente deve usar uma constante simbólica, apenas porque a constante pode explicar o que significa. É um acordo cultural extremamente arraigado, como "0 significa conclusão bem-sucedida"? Provavelmente tudo bem. Isso significa "o primeiro item de uma coleção"? Isso pode ser inofensivo, mas se houver um método alternativo, como first()eu provavelmente prefiro.

Kilian Foth
fonte
1
"Você está usando 0 como um valor sentinela?" <- Você pode explicar o que você quer dizer com "sentinela" aqui? Não consigo encontrar uma definição que pareça corresponder.
Roger.ap #
3

Todo número sem nome que não é imediatamente óbvio no contexto é um número mágico. É um pouco tolo definir números que tenham significado imediatamente óbvio no contexto.

No django (python web framework), posso definir algum campo do banco de dados com um número bruto, como:

firstname = models.CharField(max_length=40)
middlename = models.CharField(max_length=40)
lastname =  models.CharField(max_length=40) 

que é mais claro (e a prática recomendada ) do que dizer

MAX_LENGTH_NAME = 40
...
firstname = models.CharField(max_length=MAX_LENGTH_NAME)
middlename = models.CharField(max_length=MAX_LENGTH_NAME)
lastname =  models.CharField(max_length=MAX_LENGTH_NAME) 

como é improvável que eu precise alterar o comprimento (e sempre posso comparar com o max_lengthdo campo). Se eu precisar alterar o comprimento do campo depois de implantar o aplicativo inicialmente, preciso alterá-lo exatamente em um local por campo no meu código django e depois escrever adicionalmente uma migração para alterar o esquema do banco de dados. Se eu precisar fazer referência a max_lengthum campo definido de um tipo de objeto, posso fazê-lo diretamente - se esses campos estavam definindo uma Personclasse, posso usar Person._meta.get_field('firstname').max_lengthpara obter omax_lengthsendo usado (definido em um único local). O fato de os mesmos 40 terem sido usados ​​para vários campos é irrelevante, pois posso alterá-los independentemente. O comprimento do nome próprio nunca deve depender do tamanho do nome do meio ou do sobrenome; eles são valores separados e podem mudar independentemente.

Frequentemente, os índices de matriz podem usar números sem nome; como se eu tivesse um arquivo CSV de dados que desejo colocar em um dicionário python, com o primeiro elemento na linha como o dicionário que keyeu escreveria:

mydict = {}
for row in csv.reader(f):
    mydict[row[0]] = row[1:]

Claro que eu poderia nomear index_column = 0e fazer algo como:

index_col = 0
mydict = {}
for row in csv.reader(f):
    mydict[row[index_col]] = row[:index_col] + row[index_col+1:]

ou pior, defina after_index_col = index_col + 1se livrar do index_col+1, mas isso não torna o código mais claro na minha opinião. Além disso, se eu der o index_colnome a, é melhor fazer o código funcionar, mesmo que a coluna não seja 0 (daí a row[:index_col] +parte).

dr jimbob
fonte
7
Na verdade, max_lngth=40vs. max_length=MAX_LENGTH_NAMEé um exemplo clássico de um número mágico que grita para ser um símbolo. Chegará o dia em que você deseja suportar 45 nomes de caracteres e agora todo uso de "40" é suspeito e deve ser cuidadosamente examinado.
Ross Patterson
1
@ RossPatterson - Este não é o C onde comparamos constantemente com um var global MAX_ARRAY_SIZE, mas com uma estrutura da web decente. O único lugar em que o número mágico aparece é onde você declara o modelo do banco de dados; todo o resto é comparado com esse valor (por exemplo, 40 não aparece em nenhum outro lugar no código). Observe também que você não pode alterar essa variável facilmente sem fazer migrações de esquema, pois está vinculada a um banco de dados. Se eu quisesse mudar a dizer 1 de caracteres nomes do meio a sua imediatamente óbvio a um lugar a alteração no código 40para 1. Você tem que pensar no contexto.
precisa saber é o seguinte
2
Desculpe, você está errado em dois pontos. Primeiro, o OP fez uma pergunta sobre "práticas de programação" que não especifica nenhum idioma. Eles disseram "método", não "função", então vamos presumir algo orientado a objetos, mas isso não nos tira do domínio dos dados numerados por mágica. Segundo, se o número mágico é inserido no banco de dados ( por exemplo , o esquema), é ainda pior tê-lo no código. A coisa certa a fazer é obter a mágica quase constante de sua fonte - o próprio banco de dados ou um módulo de esquema que centraliza todas essas constantes que variam ao longo da vida útil do código.
Ross Patterson