Pular para o conteúdo
Atualizado em 12 min de leitura

Números mágicos e o valor das constantes nomeadas

Por Lucas Andrade ·

Um número solto no código não diz o que significa nem por que é aquele. Entenda por que valores mágicos custam caro e como constantes nomeadas os curam.

Neste artigo

Todo mundo que já leu código alheio conhece a sensação de topar com um número solto no meio de uma expressão e não fazer a menor ideia do que ele representa. Um valor aparece multiplicando, comparando ou limitando alguma coisa, sem nenhuma explicação, como se sua presença ali fosse óbvia. Para quem escreveu, no momento em que escreveu, talvez fosse; para quem lê depois, é um enigma que exige investigação. Esses valores anônimos e sem contexto têm um nome consagrado na literatura de programação: números mágicos.

O adjetivo "mágico" não é elogioso. Ele aponta para o fato de que o número funciona sem que ninguém saiba por quê, como um truque cuja mecânica está escondida. O código faz a coisa certa, aparentemente, mas a razão de ser daquele valor específico não está registrada em lugar nenhum. Ele foi escolhido por algum motivo, ligado a uma regra de negócio, a uma restrição técnica, a uma convenção externa, e esse motivo se perdeu no instante em que o número foi digitado cru na linha, sem um nome que o explicasse.

Este artigo trata de por que valores mágicos são um problema real, e não uma implicância estética, e de como as constantes nomeadas resolvem esse problema de forma simples e poderosa. Vamos ver o que caracteriza um número mágico, por que strings mágicas são igualmente nocivas, como centralizar configuração, o papel dos enums, quais exceções são aceitáveis, e por que dar um bom nome a um valor é, no fundo, um ato de documentação que a máquina verifica.

O que é um valor mágico e por que ele custa caro#

Um valor mágico é qualquer literal, número ou texto, que aparece diretamente no código sem uma explicação do que significa ou de por que tem aquele valor específico. A magia está na ausência de contexto. O número existe, funciona, e ninguém sabe dizer, só de olhar, o que ele representa nem o que aconteceria se fosse outro. Ele carrega significado, mas esconde esse significado de quem lê.

O primeiro custo dessa opacidade é de compreensão. Quem lê o código precisa parar e deduzir, a partir do contexto ao redor, o que aquele valor quer dizer. Às vezes a dedução é possível com algum esforço; muitas vezes ela é ambígua ou impossível sem consultar outra pessoa ou outra fonte. Cada número mágico é um pequeno obstáculo à leitura, e uma base de código cheia deles vira um campo minado de pausas e adivinhações que tornam a compreensão lenta e insegura.

O segundo custo, mais grave, é de manutenção. Quando o mesmo valor mágico representa a mesma regra e aparece em vários pontos do código, alterar essa regra exige encontrar e mudar todas as ocorrências. E aqui mora uma armadilha traiçoeira: o mesmo número pode aparecer por razões diferentes. Um valor que representa uma regra de negócio pode coincidir, por acaso, com outro que representa uma coisa completamente distinta. Ao fazer uma busca e substituir cega, você corre o risco de alterar ocorrências que não deveriam mudar e de esquecer as que deveriam, porque estavam escritas de forma ligeiramente diferente. O número, por não ter nome, não diz qual dos seus significados está em jogo em cada lugar.

O terceiro custo é a fragilidade diante da mudança. Regras de negócio mudam, limites são ajustados, formatos são revistos. Quando o valor que codifica essas regras está espalhado como literal cru, cada mudança é uma operação arriscada de arqueologia, em que se tenta reconstruir onde aquele valor aparece e por quê. Um sistema construído sobre números mágicos é um sistema difícil de evoluir, porque cada ajuste esbarra na incerteza sobre o significado e a distribuição dos valores.

Constantes nomeadas: dar nome ao significado#

A cura para o número mágico é quase embaraçosamente simples: dê a ele um nome. Uma constante nomeada é a associação entre um identificador descritivo e um valor, declarada em um único lugar, e usada por toda parte no lugar do literal cru. Onde antes havia um número anônimo, passa a haver um nome que anuncia o que aquele valor significa. O código deixa de fazer mágica e passa a explicar-se.

O ganho de legibilidade é imediato e substancial. Uma comparação contra um número solto obriga o leitor a adivinhar o critério; uma comparação contra uma constante com nome descritivo declara o critério em voz alta. O leitor não precisa mais deduzir; ele lê o nome e entende. Essa clareza não custa desempenho algum, porque a constante é resolvida em tempo de compilação ou tem custo irrisório, e não custa complexidade, porque é apenas um nome no lugar de um número. É um dos raros aprimoramentos que só têm vantagens.

O ganho de manutenção é ainda mais importante. Com uma constante nomeada, o valor passa a ter uma única fonte de verdade: o ponto onde ela é declarada. Se a regra mudar, você altera esse ponto e todo o sistema passa a usar o novo valor automaticamente, sem risco de esquecer uma ocorrência ou de alterar por engano um número que só coincidia. A busca por todas as ocorrências, que antes era uma operação arriscada com literais, torna-se trivial e segura, porque agora se busca por um nome único e inconfundível, não por um número ambíguo.

Há também um ganho de correção sutil. Quando o valor tem nome, o significado fica explícito, e explicitar o significado ajuda a evitar erros de uso. Fica muito mais difícil confundir dois valores que representam coisas diferentes se cada um tem um nome próprio que o distingue, ao passo que dois números idênticos escritos crus são indistinguíveis e convidam à confusão. O nome atua como um rótulo que impede que se troque uma coisa por outra sem perceber.

Strings mágicas: o mesmo problema, disfarçado#

O problema dos valores mágicos não se limita a números. Textos literais espalhados pelo código, as chamadas strings mágicas, sofrem exatamente dos mesmos males e costumam ser até mais perigosos, porque a familiaridade do texto legível dá uma falsa sensação de que ele se explica sozinho. Um nome de estado, um código de tipo, uma chave de configuração, um identificador de categoria: tudo isso, escrito cru como texto repetido em vários pontos, é uma string mágica.

O perigo específico das strings mágicas é que elas não são verificadas. Um número mágico errado pode ao menos produzir um comportamento visivelmente estranho; uma string mágica escrita com um erro de digitação sutil passa despercebida, porque para o compilador uma string é apenas uma string, e ele não sabe que aquele texto deveria pertencer a um conjunto restrito de valores válidos. Uma comparação contra um texto digitado com uma letra trocada simplesmente falha em silêncio, e o bug daí resultante é dos mais frustrantes de encontrar, porque tudo parece certo à primeira vista.

A solução é a mesma dos números: extrair as strings para constantes nomeadas, declaradas uma vez e referenciadas por toda parte. Com isso, um erro de digitação vira um erro de nome que o compilador ou a ferramenta de análise apontam de imediato, em vez de um bug silencioso em produção. E, novamente, a mudança do valor passa a ser feita em um único lugar, com propagação automática e segura para todos os usos.

Strings que representam um conjunto fechado de valores possíveis, como os estados de um pedido ou os tipos de um documento, merecem um tratamento ainda mais forte, que vai além da constante simples. Elas são candidatas naturais a enums, e a diferença que essa escolha faz na robustez do código é grande o suficiente para justificar uma seção própria.

Enums para conjuntos fechados de valores#

Quando um valor pode assumir apenas um número limitado e conhecido de opções, o enum é a ferramenta certa. Um enum define, de forma explícita, o conjunto de valores válidos para determinado conceito, e transforma o que seriam constantes soltas em um tipo próprio, com identidade e restrição. Em vez de comparar contra textos ou números que por convenção representam estados, você trabalha com um tipo que só admite os valores previstos.

A vantagem do enum sobre constantes avulsas está na garantia que ele oferece. Com constantes soltas, nada impede que alguém use um valor fora do conjunto pretendido, porque o tipo subjacente, um texto ou um número, aceita qualquer coisa. Com um enum, o próprio sistema de tipos impede o uso de um valor inválido, e muitas linguagens vão além, avisando quando um tratamento de casos esqueceu de cobrir alguma das opções possíveis. Essa cobertura exaustiva é uma rede de segurança valiosa: ao adicionar um novo estado ao enum, você é alertado sobre todos os pontos que precisam ser atualizados para lidar com ele.

O enum também melhora a comunicação do código de forma qualitativa. Um parâmetro cujo tipo é um enum diz, na própria assinatura, quais valores são aceitos, ao passo que um parâmetro de texto ou número não dá nenhuma pista sobre o conjunto pretendido. O contrato da função fica explícito no tipo, e quem a chama sabe exatamente o que pode passar, sem precisar consultar documentação ou ler a implementação para descobrir quais textos mágicos são aceitáveis.

Adotar enums para conjuntos fechados é, portanto, uma forma superior de eliminar valores mágicos, porque combina a nomeação com a restrição. Você ganha os nomes descritivos das constantes e, por cima, a garantia de que nenhum valor fora do conjunto será usado por engano. Para qualquer conceito que seja intrinsecamente uma escolha entre alternativas conhecidas, o enum deveria ser o padrão, e não a exceção.

Centralizar configuração em vez de espalhar#

Uma categoria especial de valores merece atenção separada: os que são propriamente configuração, aqueles que definem o comportamento do sistema e que variam conforme o ambiente ou a política de operação. Tempos de espera, tamanhos de limite, endereços de serviços, chaves de funcionalidade, quantidades máximas: esses valores não deveriam viver espalhados como literais pelo código, e sim ser centralizados em um ponto de configuração claramente identificado.

A razão para centralizar é dupla. Primeiro, esses valores mudam por motivos operacionais, e não por mudanças na lógica do programa, então precisam ser fáceis de encontrar e ajustar sem vasculhar o código inteiro. Um limite de tamanho ou um tempo de espera que precise ser afinado em produção deve estar em um lugar previsível, não escondido numa linha qualquer. Segundo, muitos desses valores variam entre ambientes, e mantê-los centralizados permite trocá-los conforme o contexto sem tocar na lógica, que permanece a mesma em todos os ambientes.

Centralizar configuração também impõe disciplina sobre o que é, de fato, configurável. Ao reunir esses valores em um lugar, fica visível o conjunto de parâmetros que governam o comportamento do sistema, o que ajuda a raciocinar sobre ele como um todo. Valores mágicos espalhados escondem essa superfície de configuração; um ponto central a revela, tornando explícito quais são as alavancas que ajustam o comportamento e quais são os valores atuais de cada uma.

Vale distinguir configuração de constantes de domínio. Nem todo valor nomeado precisa ir para um arquivo de configuração externo. Constantes que são fixas por natureza, que decorrem de uma regra imutável ou de uma definição matemática, pertencem ao código, como constantes nomeadas próximas de onde são usadas. Configuração é o que varia por decisão operacional ou por ambiente. Misturar as duas coisas, tratando uma constante fixa como configuração ou um parâmetro operacional como constante enterrada, é um erro que confunde a natureza dos valores.

Exceções aceitáveis e a arte de nomear bem#

Seria um exagero afirmar que todo literal precisa virar constante. Existem exceções amplamente aceitas, e reconhecê-las evita o extremo oposto, o da nomeação obsessiva que cria constantes inúteis. Os casos mais claros são os valores cujo significado é universal e evidente no contexto, como o zero e o um em operações elementares. Somar um a um contador, começar um índice em zero, verificar se uma coleção está vazia comparando seu tamanho a zero: nesses usos, o número não carrega uma regra oculta, ele é o próprio conceito elementar, e nomeá-lo só adicionaria ruído.

O critério para distinguir a exceção legítima do número mágico verdadeiro é perguntar se o valor codifica uma decisão que poderia ter sido outra. O um que incrementa um contador não poderia ser outro número sem mudar o significado da operação de contar; ele é intrínseco. Já um limite, uma alíquota, um tamanho máximo, um tempo de espera, tudo isso poderia razoavelmente ter outro valor, e foi escolhido por um motivo. É esse motivo, a decisão por trás do valor, que o nome precisa capturar. Onde há decisão, há nome a dar; onde há apenas o conceito elementar, o literal se explica.

Quando se decide nomear, a qualidade do nome faz toda a diferença, e um bom nome revela o significado e, quando aplicável, a unidade. Um valor que representa um tempo deveria deixar claro se está em segundos ou em milissegundos, porque a ambiguidade de unidade é fonte notória de bugs. Um valor que representa uma distância, uma quantidade, uma proporção, deveria carregar no nome a natureza do que mede. Nomes que revelam unidade e significado transformam a constante em documentação precisa, ao passo que nomes vagos apenas trocam um número anônimo por um rótulo pouco melhor.

No conjunto, o esforço de nomear valores é um investimento contínuo em manutenibilidade. Cada número mágico que vira uma constante bem nomeada é uma pequena dívida de compreensão que se quita, um ponto de fragilidade de manutenção que se elimina, uma decisão de negócio que passa a estar registrada de forma explícita e verificável. Multiplicado por toda uma base de código, esse cuidado é a diferença entre um sistema que qualquer pessoa consegue ler e evoluir e um sistema que só o autor original entende, e apenas enquanto a memória dele durar. Dar nome ao que significa algo é, no fim, uma das formas mais baratas e mais duradouras de tornar o software honesto sobre suas próprias intenções.

Leituras relacionadas

Nenhum comentário ainda

Seja o primeiro a comentar.

Deixe seu comentário

Entre com sua conta Canverly para comentar. Você pode usar a mesma conta em qualquer site da rede.

Entrar com Canverly