Eliminando números mágicos: quando é hora de dizer "não"?

36

Todos sabemos que os números mágicos (valores codificados) podem causar estragos no seu programa, especialmente quando é hora de modificar uma seção de código que não tem comentários, mas onde você desenha a linha?

Por exemplo, se você possui uma função que calcula o número de segundos entre dois dias, substitui

seconds = num_days * 24 * 60 * 60

com

seconds = num_days * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE

Em que momento você decide que é completamente óbvio o que o valor codificado significa e o deixa em paz?

oosterwal
fonte
2
Por que não substituir esse cálculo com uma função ou macro para que seus fins código parecendoseconds = CALC_SECONDS(num_days);
FrustratedWithFormsDesigner
15
TimeSpan.FromDays(numDays).Seconds;
Ninguém
18
@oosterwal: Com essa atitude ( HOURS_PER_DAY will never need to be altered), você nunca estará codificando para o software implantado em Marte. : P
FrustratedWithFormsDesigner
23
Eu teria reduzido o número de constantes para apenas SECONDS_PER_DAY = 86400. Por que calcular algo que não muda?
31411 JohnFx
17
E quanto aos segundos bissextos?
John

Respostas:

40

Há duas razões para usar constantes simbólicas em vez de literais numéricos:

  1. Simplificar a manutenção se os números mágicos mudarem. Isso não se aplica ao seu exemplo. É extremamente improvável que o número de segundos em uma hora ou o número de horas em um dia mude.

  2. Para melhorar a legibilidade. A expressão "24 * 60 * 60" é bastante óbvia para quase todos. "SECONDS_PER_DAY" também é, mas se você estiver procurando um bug, talvez seja necessário verificar se SECONDS_PER_DAY foi definido corretamente. Há valor na brevidade.

Para números mágicos que aparecem exatamente uma vez e são independentes do resto do programa, decidir se deve criar um símbolo para esse número é uma questão de gosto. Se houver alguma dúvida, vá em frente e crie um símbolo.

Não faça isso:

public static final int THREE = 3;
Kevin Cline
fonte
3
+1 @kevin cline: Concordo com o seu ponto de vista sobre a brevidade de uma caça a insetos. O benefício adicionado ao uso de uma constante nomeada, especialmente durante a depuração, é que, se for descoberto que uma constante foi definida incorretamente, você precisará alterar apenas um trecho de código em vez de pesquisar em todo o projeto todas as ocorrências da implementação incorreta. valor.
Oosterwal
40
Ou pior ainda:publid final int FOUR = 3;
gablin
3
Oh, querida, você deve ter trabalhado com o mesmo cara com quem eu trabalhei.
quickly_now
2
@ gablin: Para ser justo, é bastante útil que os bares tenham tampas.
Alan Pearce
10
Eu já vi isso: public static int THREE = 3;... nota - não final!
Stephen C
29

Eu manteria a regra de nunca ter números mágicos.

Enquanto

seconds = num_days * 24 * 60 * 60

É perfeitamente legível na maioria das vezes, depois de ter codificado por 10 horas por dia durante três ou quatro semanas no modo de trituração

seconds = num_days * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE

é muito mais fácil de ler.

A sugestão de FrustratedWithFormsDesigner é melhor:

seconds = num_days * DAYS_TO_SECOND_FACTOR

ou melhor ainda

seconds = CONVERT_DAYS_TO_SECONDS(num_days)

As coisas deixam de ser óbvias quando você está muito cansado. Código defensivamente .

Vitor Py
fonte
13
Entrar no modo crunch como você descreve é ​​um antipadrão contraproducente que deve ser evitado. Os programadores atingem o pico de produtividade sustentada em cerca de 35 a 40 horas / semana.
btilly
4
@btilly Concordo plenamente com você. Mas isso acontece, geralmente devido a fatores externos.
Vitor Py
3
Eu geralmente defino constantes para segundo, minuto, dia e hora. Se nada mais '30 * MINUTE 'é realmente fácil de ler e eu sei que é uma hora sem ter que pensar nisso.
Zachary K
9
@btilly: O pico é de 35 a 40 horas ou um nível de álcool no sangue entre 0,129% e 0,138%. Eu li no XKCD , então deve ser verdade!
Oosterwal
1
Se eu visse uma constante como HOURS_PER_DAY, eu a excluiria e depois humilharia você publicamente na frente de seus colegas. Ok, talvez eu renuncie à humilhação pública, mas provavelmente a excluiria.
Ed S.
8

A hora de dizer não é quase sempre. Os momentos em que acho mais fácil usar números codificados são em locais como o layout da interface do usuário - a criação de uma constante para o posicionamento de cada controle no formulário fica muito cansativa e cansativa e, se esse código geralmente é tratado por um designer de interface do usuário, não importa muito. ... a menos que a interface do usuário seja definida dinamicamente, ou use posições relativas para alguma âncora ou seja escrita à mão. Nesse caso, eu diria que é melhor definir algumas constantes significativas para o layout. E se você precisar de um fator de correção aqui ou ali para alinhar / posicionar algo "correto", isso também deve ser definido.

Mas no seu exemplo, acho que substituir 24 * 60 * 60por DAYS_TO_SECONDS_FACTORé melhor.


Concordo que os valores codificados também são bons quando o contexto e o uso são completamente claros. Esta, no entanto, é uma decisão judicial ...

Exemplo:

Como @rmx apontou, usar 0 ou 1 para verificar se uma lista está vazia ou talvez nos limites de um loop é um exemplo de caso em que o objetivo da constante é muito claro.

FrustratedWithFormsDesigner
fonte
2
Geralmente, é bom usar 0ou 1acho. if(someList.Count != 0) ...é melhor que if(someList.Count != MinListCount) .... Nem sempre, mas geralmente.
Ninguém
2
@ Dima: o designer de formulários do VS lida com tudo isso. Se ele quer criar constantes, tudo bem para mim. Mas não vou entrar no código gerado e substituir todos os valores codificados por constantes.
FrustratedWithFormsDesigner
4
Não vamos confundir código destinado a ser gerado e manipulado por uma ferramenta com código escrito para consumo humano.
biziclop
1
@FrustratedWithFormsDesigner como @biziclop apontou, o código gerado é um animal totalmente diferente. As constantes nomeadas absolutamente devem ser usadas no código que é lido e modificado pelas pessoas. O código gerado, pelo menos no caso ideal, não deve ser modificado.
Dima
2
@FrustratedWithFormsDesigner: O que acontece quando você tem um valor conhecido codificado em dezenas de arquivos em seu programa que de repente precisam ser alterados? Por exemplo, você codifica o valor que representa o número de ticks de clock por microssegundo para um processador incorporado e é instruído a portar seu software para um design em que haja um número diferente de ticks de clock por microssegundo. Se o seu valor fosse algo comum, como 8, executar uma localização / substituição em dezenas de arquivos poderia acabar causando mais problemas.
Oosterwal
8

Pare quando não conseguir definir um significado ou objetivo para o número.

seconds = num_days * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE

é muito mais fácil de ler do que apenas usar os números. (Embora possa ser mais legível por ter uma única SECONDS_PER_DAYconstante, mas é uma questão completamente separada.)

Suponha que um desenvolvedor que observe o código possa ver o que ele faz. Mas não assuma que eles também sabem o porquê. Se a sua constante ajuda a entender o porquê, vá em frente. Se não, não.

Se você acabar com muitas constantes, como sugerido por uma resposta, considere usar um arquivo de configuração externo, pois ter dezenas de constantes em um arquivo não melhora exatamente a legibilidade.

Biziclop
fonte
7

Eu provavelmente diria "não" a coisas como:

#define HTML_END_TAG "</html>"

E definitivamente diria "não" para:

#define QUADRATIC_DISCRIMINANT_COEF 4
#define QUADRATIC_DENOMINATOR_COEF  2
dan04
fonte
7

Um dos melhores exemplos que encontrei para promover o uso de constantes para coisas óbvias como HOURS_PER_DAYé:

Estávamos calculando quanto tempo as coisas estavam na fila de trabalho de uma pessoa. Os requisitos foram vagamente definidos e o programador foi codificado 24em vários locais. Eventualmente, percebemos que não era justo punir os usuários por permanecerem em um problema por 24 horas, quando realmente funcionam apenas 8 horas por dia. Quando chegou a tarefa de corrigir isso E ver quais outros relatórios poderiam ter o mesmo problema, era muito difícil fazer grep / pesquisar no código de 24 teria sido muito mais fácil fazer grep / procurarHOURS_PER_DAY

bkulyk
fonte
oh não, então as horas por dia variam de acordo com o horário de trabalho por dia (eu trabalho 7,5 BTW) ou as horas por dia. Para alterar o significado da constante assim, convém substituir o nome dela por outra. Seu argumento sobre a busca facilitada é válido.
Gbjbaanb
2
Parece que, neste caso, HOURS_PER_DAY também não era realmente a constante desejada. Mas poder procurá-lo pelo nome é um benefício enorme, mesmo que (ou especialmente se) você precise alterá-lo para outra em muitos lugares.
David K
4

Penso que, desde que o número seja completamente constante e não tenha possibilidade de mudar, é perfeitamente aceitável. Portanto, no seu caso, tudo seconds = num_days * 24 * 60 * 60bem (supondo que você não faça algo bobo como fazer esse tipo de cálculo dentro de um loop) e sem dúvida melhor para facilitar a leitura do que seconds = num_days * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE.

É quando você faz coisas assim que é ruim:

lineOffset += 24; // 24 lines to a page

Mesmo que você não consiga encaixar mais linhas na página ou mesmo que não tenha a intenção de alterá-la, use uma variável constante, pois um dia ele voltará para assombrá-lo. Por fim, o objetivo é a legibilidade, não economizando 2 ciclos de cálculo na CPU. Já não é 1978, quando bytes preciosos foram extraídos por todo o seu valor.

Neil
fonte
2
Você acha aceitável codificar o valor inalterável 86400 em vez de usar a constante denominada SECONDS_PER_DAY? Como você verificaria se todas as ocorrências do valor estavam corretas e sem o 0 ou trocando o 6 e o ​​4 por exemplo?
Oosterwal
Então por que não: segundos = num_dias * 86400? Isso também não vai mudar.
JeffO 10/03/11
2
Eu fiz a coisa SECONDS_PER_DAY nos dois sentidos - usando o nome e o número. Quando você volta ao código 2 anos depois, o número nomeado SEMPRE faz mais sentido.
quickly_now
2
seconds = num_days * 86400 não está claro para mim. Isso é o que conta em última análise. Se eu vir "segundos = num_dias * 24 * 60 * 60", além do fato de que os nomes das variáveis ​​emprestam o significado razoavelmente bem nesse caso, eu me perguntaria imediatamente por que os separei e o significado se tornará óbvio porque eu saí eles como números (portanto, são constantes), não variáveis ​​para as quais uma investigação mais aprofundada exigiria que eu entendesse seus valores e se eles são constantes.
1111 Neil
1
O que as pessoas geralmente não percebem: se você alterar esse valor de lineOffset de 24 para 25, terá que passar por todo o seu código para ver onde 24 foi usado e se ele precisa ser alterado e, em seguida, todos os cálculos de dias para horas multiplicar por 24 realmente entra no seu caminho.
precisa saber é o seguinte
3
seconds = num_days * 24 * 60 * 60

Está perfeitamente bem. Esses números não são realmente mágicos, pois nunca mudarão.

Quaisquer números que possam mudar razoavelmente ou não tenham significado óbvio devem ser colocados em variáveis. O que significa praticamente todos eles.

Carra
fonte
2
Seria seconds = num_days * 86400ainda ser aceitável? Se um valor como esse fosse usado várias vezes em vários arquivos diferentes, como você verificaria se alguém não digitou acidentalmente seconds = num_days * 84600um ou dois lugares?
Oosterwal
1
Escrever 86400 é muito diferente de escrever 24 * 60 * 60.
Carra
4
Claro que vai mudar. Nem todo dia tem 86.400 segundos. Considere, por exemplo, horário de verão. Uma vez por ano, alguns lugares têm apenas 23 horas em um dia, e outro dia eles terão 25. poof seus números estão quebrados.
Dave DeLong
1
@ Dave, excelente ponto. Existem segundos de pulo - pt.wikipedia.org/wiki/Leap_second
Ponto justo. Adicionar uma função seria uma salvaguarda se você precisar capturar essas exceções.
Carra
3

Eu evitaria criar constantes (valores mágicos) para converter um valor de uma unidade para outra. Em caso de conversão, prefiro um nome de método de fala. Neste exemplo, isso seria DayToSeconds(num_days)interno, por exemplo, o método não precisa de valores mágicos porque o significado de "24" e "60" é claro.

Nesse caso, eu nunca usaria segundos / minutos / horas. Eu usaria apenas TimeSpan / DateTime.

KayS
fonte
1

Use o contexto como um parâmetro para decidir

Por exemplo, você tem uma função chamada "calculaSegundosBetween: aDay e: anotherDay", não será necessário fazer muita explicação sobre o que esses números fazem, porque o nome da função é bastante representativo.

E outra pergunta é: quais são as possibilidades de calculá-lo de uma maneira diferente? Às vezes, existem muitas maneiras de fazer a mesma coisa; portanto, para orientar futuros programadores e mostrar a eles qual método você usou, definir constantes pode ajudar a descobrir isso.

guiman
fonte