Todos os números mágicos são criados da mesma forma?

77

Em um projeto recente, eu precisei converter de bytes para kilobytes kibibyte . O código era direto o suficiente:

var kBval = byteVal / 1024;

Depois de escrever isso, peguei o restante da função funcionando e segui em frente.

Mais tarde, porém, comecei a me perguntar se havia acabado de incorporar um número mágico ao meu código. Parte de mim diz que foi bom porque o número é uma constante fixa e deve ser facilmente entendido. Mas outra parte de mim pensa que seria super claro se envolto em uma constante definida como BYTES_PER_KBYTE.

Então, os números que são constantes conhecidas são realmente tão mágicos ou não?


Perguntas relacionadas:

Quando um número é um número mágico? e Todo número no código é considerado um "número mágico"? - são semelhantes, mas são perguntas muito mais amplas do que o que estou perguntando. Minha pergunta está focada em números constantes bem conhecidos, que não são abordados nessas perguntas.

Eliminando números mágicos: quando é hora de dizer "não"? também está relacionado, mas está focado na refatoração, em vez de se um número constante é ou não um número mágico.

Comunidade
fonte
17
Na verdade, eu trabalhei em um projeto onde eles criaram constantes como FOUR_HUNDRED_FOUR = 404,. Eu trabalhei em outro projeto onde eles eram militantes sobre o uso de strings constantes em vez de literais, então eles tinham dezenas de linhas no código que pareciam:DATABASE = "database"
Rob
82
Definitivamente use 1024, porque, caso contrário, sua equipe de desenvolvimento passará todo o tempo discutindo sobre se são "kilobytes" ou "kibibytes".
Gort the Robot
6
Você pode considerar que 1024 é kibi e #define KIBI1024, MEBIcomo 1024 * 1024 ...
ysdx 18/12/2014
6
@Rob Y: soa como bons velhos programadores de Fortran. Porque essa linguagem de programação forçou os programadores a fazer isso. Sim, você verá constantes como ZERO=0, ONE=1, TWO=2e quando os programas forem portados para outras línguas (ou os programadores não mudam de comportamento ao mudar de idioma), você também verá lá e terá que rezar para que nunca alguém mude para ONE=2...
Holger
4
@NoctisSkytower Minha equipe prefere usar instruções de divisão explícitas em vez de operadores de mudança de bits devido aos vários idiomas que usamos e à implementação potencialmente inconsistente nesses idiomas. Da mesma forma, valores negativos são tratados de maneira inconsistente com a mudança bit a bit. Embora possamos não ter necessariamente valores de bytes negativos, certamente temos valores negativos com outras unidades de medida que convertemos.

Respostas:

103

Nem todos os números mágicos são iguais.

Eu acho que nesse caso, essa constante está OK. O problema com os números mágicos é quando eles são mágicos, ou seja, não está claro qual é sua origem, por que o valor é o que é ou se o valor está correto ou não.

Ocultar 1024 atrás de BYTES_PER_KBYTE também significa que você não vê instantaneamente se está correto ou não.

Eu esperaria que alguém soubesse imediatamente por que o valor é 1024. Por outro lado, se você estivesse convertendo bytes em megabytes, definiria a constante BYTES_PER_MBYTE ou semelhante, porque a constante 1.048.576 não é tão óbvia que 1024 ^ 2, ou que está correto.

O mesmo vale para valores ditados por requisitos ou padrões, que são usados ​​apenas em um só lugar. Acho que apenas colocar a constante no lugar com um comentário para a fonte relevante é mais fácil de lidar do que defini-lo em outro lugar e ter que perseguir as duas partes, por exemplo:

// Value must be less than 3.5 volts according to spec blah.
SomeTest = DataSample < 3.50

Acho melhor que

SomeTest = DataSample < SOME_THRESHOLD_VALUE

Somente quando SOME_THRESHOLD_VALUEé usado em vários lugares é que a troca vale a pena definir uma constante, na minha opinião.

whatsisname
fonte
67
"O problema com números mágicos é quando eles são mágicos" - Esta é tal uma explicação brilhante de que conceito! Estou falando sério! +1 apenas nessa frase.
Jörg W Mittag
20
Aqui está uma que eu acabei de descobrir: "não é o número que é o problema, é a mágica".
Jörg W Mittag
10
1024 é óbvio para quem? Não é essa a justificativa para todo número mágico? Todos os números mágicos são usados ​​porque são óbvios para quem os escreveu. O 9.8 também não é óbvio? Para mim, é bastante óbvio que é a aceleração da gravidade na Terra, mas, mesmo assim, eu criaria uma constante, porque o que é óbvio para mim pode não ser óbvio para outra pessoa.
Tulains Córdova
15
Não. Um comentário como o do seu exemplo "melhor" é uma enorme bandeira vermelha. É um código que nem passa no teste de legibilidade da pessoa que o escreveu no momento. Eu vou dar um exemplo. e^i*pi = -1é muito mais explícito (melhor) que 2.718^i*3.142 = -1. As variáveis ​​são importantes e não são apenas para código comum. O código é escrito para leitura primeiro, compilação em segundo. Além disso, as especificações mudam (muito). Enquanto o 1024 provavelmente não deveria estar na configuração, o 3.5 soa como deveria.
Nathan Cooper
51
Também não usaria uma constante para 1024 ^ 2; 1024*1024plz!
Lightness Races in Orbit
44

Há duas perguntas que faço quando se trata de números mágicos.

O número tem um nome?

Os nomes são úteis porque podemos ler o nome e entender a finalidade do número por trás dele. Nomear constantes pode aumentar a legibilidade se o nome for mais fácil de entender do que o número que substitui e o nome da constante for conciso.

Claramente, constantes como pi, e, et al. tem nomes significativos. Um valor como 1024 poderia ser, BYTES_PER_KBmas eu também esperaria que qualquer desenvolvedor soubesse o que 1024 significa. O público-alvo do código-fonte são programadores profissionais que devem ter experiência em conhecer vários poderes de dois e por que eles são usados.

É usado em vários locais?

Enquanto os nomes são uma força de constantes, outra é a reutilização. Se é provável que um valor mude, ele pode ser alterado em um local, em vez de precisar caçá-lo em vários locais.

Sua pergunta

No caso de sua pergunta, eu usaria o número como está.

Nome: existe um nome para esse número, mas não é nada realmente útil. Não representa uma constante ou valor matemático especificado em nenhum documento de requisitos.

Locais: mesmo se usado em vários locais, nunca será alterado, negando esse benefício.


fonte
1
A razão para usar constantes em vez de números mágicos não é apenas porque os números serão alterados, mas também para facilitar a leitura e a autodocumentação.
Tulains Córdova
4
@ user61852: constantes nomeadas nem sempre são mais legíveis. Eles costumam ser, mas nem sempre.
Whatsisname
2
Pessoalmente, eu uso essas duas perguntas: "Esse valor será alterado na vida útil do programa?" e "Os desenvolvedores que espero trabalhar neste software entenderão para que serve esse número?"
Gort the Robot
4
Você quer dizer o problema do ano 2000? Não tenho certeza se é relevante aqui. Sim, havia muitos códigos como 'date - 1900', mas nesse código, o problema não era o número mágico "1900".
Gort the Robot
1
Esta resposta pode se beneficiar da menção de que alguns números "óbvios", 1024 definitivamente sendo um, são tais que outros desenvolvedores provavelmente os escrevem espontaneamente como números, mesmo quando alguém define uma constante nomeada para eles. Eu, pelo menos provavelmente nem pensaria em procurar no código-fonte a constante existente para 1024 se ainda não soubesse que existe um, se precisasse usar 1024 na conversão de quantidade de bytes.
Hyde
27

Esta citação

Não é o número que é o problema, é a mágica.

como dito por Jörg W Mittag responde muito bem a essa pergunta.

Alguns números simplesmente não são mágicos dentro de um contexto específico. No exemplo fornecido na pergunta, as unidades de medida foram especificadas pelos nomes das variáveis ​​e a operação que estava ocorrendo era bastante clara.

Portanto, 1024não é mágico, porque o contexto deixa muito claro que é o valor apropriado e constante a ser usado nas conversões.

Da mesma forma, um exemplo de:

var numDays = numHours / 24; 

é igualmente claro e não é mágico, porque é sabido que existem 24 horas no dia.

Comunidade
fonte
21
Mas ... mas ... 24 podem mudar! A Terra está diminuindo sua rotação e, eventualmente, terá 25 horas! (É claro que todos nós estaremos mortos até então, fazendo a manutenção desse software para o problema de outra pessoa) #
14
O que acontece depois que seu software é implantado em Marte? Você deve estar injetando essa constante ...
durron597
8
@ durron597: e se o seu programa for executado o tempo suficiente para a Terra diminuir durante esse período . Você não deve ser a injeção de um, em vez de uma função constante que aceita um timestamp (default agora) e retorna o número de horas no dia em que timestamp cai ;-)
Steve Jessop
13
Você precisará aprender YAGNI.
Whatsisname
3
@ durron597 Nada de especial acontece quando o software de cronometragem é implantado em Marte, porque, por convenção, os dias de Marte têm 24 horas de duração, mas cada hora é 2,7% mais longa do que na Terra . Obviamente, nem um dia sideral da Terra nem um dia solar da Terra são exatamente 24 horas (os números exatos estão na mesma página), então você não pode usá-lo de 24 qualquer maneira! Como Izkata mencionou, os segundos bissextos doem. Talvez você tenha mais sorte usando a constante 24em Marte do que na Terra!
um CVn
16

Outros pôsteres mencionaram que a conversão está sendo "óbvia", mas eu discordo. A pergunta original, neste momento, inclui:

kilobytes kibibytes

Então, eu já sei que o autor está ou estava confuso. A página da Wikipedia aumenta a confusão:

1000 = KB kilobyte (metric)
1024 = kB kilobyte (JEDEC)
1024 = KiB kibibyte (IEC)

Portanto, "Kilobyte" pode ser usado para significar um fator de 1000 e 1024, com a única diferença em taquigrafia sendo a capitalização do 'k'. Além disso, 1024 pode significar kilobyte (JEDEC) ou kibibyte (IEC). Por que não destruir toda essa confusão com uma constante com um nome significativo? BTW, este segmento usou "BYTES_PER_KBYTE" com freqüência, e isso não é menos ambíguo. KBYTE: é KIBIBYTE ou KILOBYTE? Eu preferiria ignorar o JEDEC e ter BYTES_PER_KILOBYTE = 1000e BYTES_PER_KIBIBYTE = 1024. Não há mais confusão.

A razão pela qual pessoas como eu, e muitas outras por aí, têm opiniões 'militantes' (para citar um comentarista aqui) sobre como nomear números mágicos, é tudo sobre documentar o que você pretende fazer e remover a ambiguidade. E você realmente escolheu uma unidade que levou a muita confusão.

Se eu ver:

int BYTES_PER_KIBIBYTE = 1024;  
...  
var kibibytes = bytes / BYTES_PER_KIBIBYTE;  

Então fica imediatamente óbvio o que o autor pretendia fazer, e não há ambiguidade. Posso verificar a constante em questão de segundos (mesmo que esteja em outro arquivo), portanto, mesmo que não seja 'instantâneo', ele está próximo o suficiente para ser instantâneo.

No final, pode ser óbvio quando você está escrevendo, mas será menos óbvio quando você voltar mais tarde, e pode ser ainda menos óbvio quando alguém o editar. Demora 10 segundos para fazer uma constante; pode levar meia hora ou mais para depurar um problema com as unidades (o código não vai aparecer em você e dizer que as unidades estão erradas, você terá que fazer as contas sozinho para descobrir isso, e você provavelmente procurará 10 avenidas diferentes antes de verificar as unidades).

Shaz
fonte
2
Boa resposta do contador. Seria mais forte se você levasse em conta a cultura da equipe individual. Se você acreditou no meu perfil SE , tenho idade suficiente para anteceder esses padrões específicos. Portanto, a única confusão vem de "qual é o termo atual (não) padrão?" E você provavelmente estaria seguro em assumir que eu trabalho com uma equipe de colegas dinossauros que todos têm a mesma terminologia (não) dificuldades.
@ GlenH7: IMHO, as unidades de potência de duas unidades deveriam ter sido mantidas para armazenamento, uma vez que são alocadas em partes de tamanho de potência de duas. O tamanho mínimo de alocação é de 4096 bytes, faz mais sentido ter uma unidade para a quantidade de armazenamento necessária para armazenar 256 arquivos de tamanho mínimo ou a quantidade de armazenamento necessária para armazenar 244.140625 desses arquivos? Pessoalmente, vejo que a diferença entre megabytes do fabricante do disco rígido e outros megabytes é análoga à diferença entre as polegadas diagonais do aparelho de televisão e as polegadas diagonais reais.
Supercat
@ Ryan: Neste caso específico, eu preferiria ser militante sobre a adoção de unidades padrão - KB é de 1000 bytes ou o código está errado e 1024 bytes é KiB ou o código está errado. Essa é a única maneira de superar o problema das "unidades ambíguas". Pessoas diferentes que definem "constantes mágicas" (como KB) diferentemente não ajudarão.
Brendan
11

Definir um nome como referindo-se a um valor numérico sugere que sempre que um valor diferente for necessário em um local que use esse nome, provavelmente será necessário em todos. Também tende a sugerir que alterar o valor numérico atribuído ao nome é uma maneira legítima de alterar o valor. Essa implicação pode ser útil quando é verdadeira e perigosa quando é falsa.

O fato de dois locais diferentes usarem um valor literal específico (por exemplo, 1024) sugerirá fracamente que as mudanças que levariam um programador a mudar um provavelmente inspirariam o programador a querer mudar outros, mas essa implicação é muito mais fraca do que seria aplicável. se o programador atribuiu um nome a essa constante.

Um grande perigo com algo assim #define BYTES_PER_KBYTE 1024é que isso pode sugerir a alguém que encontrar printf("File size is %1.1fkB",size*(1.0/BYTES_PER_KBYTE));que uma maneira segura de fazer o código usar milhares de bytes seria alterar a #definedeclaração. Essa mudança pode ser desastrosa, no entanto, se, por exemplo, algum outro código não relacionado receber o tamanho de um objeto em Kbytes e usar essa constante ao alocar um buffer para ele.

Pode ser razoável usar #define BYTES_PER_KBYTE_FOR_USAGE_REPORT 1024e #define BYTES_PER_KBYTE_REPORTED_BY_FNOBULATOR 1024atribuir um nome diferente para cada finalidade diferente servida pela constante 1024, mas isso resultaria em muitos identificadores sendo definidos e usados ​​exatamente uma vez. Além disso, em muitos casos, é mais fácil entender o que um valor significa se alguém vê o código onde é usado, e é mais fácil descobrir onde o código significa se alguém vê os valores de quaisquer constantes usadas nele. Se um literal numérico é usado apenas uma vez para uma finalidade específica, escrever o literal no local em que é usado geralmente gera um código mais compreensível do que atribuir um rótulo a ele em um local e usar seu valor em outro lugar.

supercat
fonte
7

Eu me inclinaria a usar apenas o número, mas acho que uma questão importante não foi levantada: o mesmo número pode significar coisas diferentes em contextos diferentes, e isso pode complicar a refatoração.

1024 também é o número de KiB por MiB. Suponha que usamos 1024 para também representar esse cálculo em algum lugar ou em vários lugares, e agora precisamos mudar para calcular o GiB. Alterar a constante é mais fácil do que uma localização / substituição global, onde você pode acidentalmente alterar a incorreta em alguns lugares, ou perder em outros.

Ou pode ser uma máscara introduzida por um programador preguiçoso que precisa ser atualizado um dia.

É um exemplo um pouco artificial, mas em algumas bases de código isso pode causar problemas ao refatorar ou atualizar para novos requisitos. Para este caso em particular, porém, eu não consideraria o número simples uma forma realmente ruim, especialmente se você puder incluir o cálculo em um método de reutilização, provavelmente faria isso sozinho, mas consideraria a constante mais 'correta'.

Porém, se você usa constantes nomeadas, como o supercat diz, é importante considerar se o contexto também é importante e se você precisa de vários nomes.

Nick P
fonte