Olá, leitores, tudo certo?
Já ouviram falar em “mágica” na programação? Embora seja um termo que nos lembre algo positivo, na realidade, é um equÃvoco que desenvolvedores cometem no código, principalmente quando o projeto é colaborativo. A ideia desse artigo é detalhar um pouco mais sobre Magic Numbers e String Literals e, claro, apresentar sugestões para evitá-los!
Definição
Leitores, as dicas presentes neste artigo são básicas, mas contribuem para um código com mais qualidade e profissionalismo, afinal, é isso que almejamos em nossos projetos.
Magic Numbers e String Literals são, respectivamente, números e textos escritos diretamente no código, dificultando duas atividades: interpretação e manutenção. Considere o código abaixo:
1 2 3 4 |
if DataSet.FieldByName('Embalagem').AsInteger = 5 then begin result := CalcularFrete(3); end; |
É difÃcil compreender o que este método retorna. O que significa o número 5? E o número 3 no cálculo do frete? É uma porcentagem? Um tipo de frete? Um valor fixo? Observe que, apesar de simples, o código se torna desnecessariamente complexo. Esse é o problema da interpretação.
A complexidade aumenta um pouco mais quando este mesmo número é utilizado em diferentes partes do sistema:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
if lEmbalagem = 5 then begin ValidarPesoCarga; end; ... if PesoMaiorQue10Kilos then begin DataSet.Edit; DataSet.FieldByName('Embalagem').AsInteger := 5; DataSet.Post; end; |
Essa repetição significa que, caso o valor do tipo da embalagem seja alterado de 5 para 6, todos as ocorrências no código terão de ser modificadas. O que aconteceria se o desenvolvedor esquecesse de alterar um desses locais? Pare um pouco e reflita! 🙂
Evitando Magic Numbers
Magic Numbers podem ser evitados ao serem substituÃdos por constantes.
Pois bem, sabe o que significa o número 5? Embalagem de madeira, meu amigo! Como adivinharÃamos?
Sendo assim, podemos melhorar o código criando duas constantes, transmitindo claramente a finalidade destes números:
1 2 3 |
const EMBALAGEM_MADEIRA = 5; PORCENTAGEM_EMBALAGEM_MADEIRA = 3; |
Agora, veja a diferença no código:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
if DataSet.FieldByName('Embalagem').AsInteger = EMBALAGEM_MADEIRA then begin result := CalcularFrete(PORCENTAGEM_EMBALAGEM_MADEIRA); end; ... if lTipo = EMBALAGEM_MADEIRA then begin ValidarPesoCarga; end; ... if PesoMaiorQue10Kilos then begin DataSet.Edit; DataSet.FieldByName('Embalagem').AsInteger := EMBALAGEM_MADEIRA; DataSet.Post; end; |
É outro nÃvel de codificação, concorda? 😉
Quando estudei sobre Magic Numbers, encontrei um desenvolvedor contestando sobre o uso de constantes no Stack Overflow:
“Eu diria que o código fica menos legÃvel com constantes. Por que eu criaria uma constante para um número que não irá mudar? Isso apenas deixa o código mais poluÃdo.”
Seriously??
Pessoal, isso não é nem questão de opinião. É profissionalismo. Eu, particularmente, já perdi muito tempo buscando o significado de Magic Numbers “enterrados” no código, algumas vezes desconhecidos até pelos Analistas de Sistemas. Se constantes fossem utilizadas, estarÃamos livres pelo menos desse horror.
Evitando String Literals
O mesmo acontece com as String Literals. Ao utilizar letras em comparações ou atribuições, por exemplo, nos deparamos com as mesmas dificuldades dos números:
1 2 3 4 |
if ConsultarSituacaoCliente = 'A' then begin ValidarLimiteDiario; end; |
Você acha que “A” significa “Ativo”? Não, é “Em Atraso”, meu caro! Imagine se utilizássemos o “A” para isentar o cliente de algumas taxas, julgando que ele estivesse ativo? Prefiro nem pensar…
Novamente, poderÃamos criar constantes como solução:
1 2 3 4 5 6 7 8 9 |
const EM_ATRASO = 'A'; ... if ConsultarSituacaoCliente = EM_ATRASO then begin ValidarLimiteDiario; end; |
Ah, antes que eu me esqueça, adquira também o hábito de converter o valor de origem para maiúsculo antes de compará-los, evitando um retorno falso em função do Case Sensitivity:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
const IMPRESSORA_FISCAL = 'FISCAL'; ... var lTipoImpressora: string; begin lTipoImpressora := UpperCase(ConsultarTipoPapel); if lTipoImpressora = IMPRESSORA_FISCAL then begin AjustarColunasCupom; end; end; |
Concatenção composta
Esse termo representa a utilização de vários “+” consecutivos para concatenar um texto, como, por exemplo, uma instrução SQL. Porém, esse excesso de concatenações também pode dificultar a interpretação da linha de código. Quer ver?
1 2 3 |
Query.SQL.Text := 'Insert into CLIENTES (Codigo, Nome, CPF, DataNasc, Cidade) values (' + StrToInt(EditCodigo.Text) + ',' + QuotedStr(EditNome.Text) + ',' + QuotedStr(EditCPF.Text) + ',' + StrToDate(EditDataNasc.Text) + ',' + QuotedStr(EditCidade.Text) + ')'; |
A leitura torna-se desconfortável, não é? Além disso, no editor de códigos, será necessário mover a barra de rolagem horizontal até o fim da linha para analisar a instrução por completo. É por isso que algumas IDEs de desenvolvimento exibem uma margem do lado direito do editor de código, com o intuito de impedir que o desenvolvedor escreva linhas extensas.
A grande maioria (ou todas) das linguagens de programação trazem vários recursos para evitar a concatenação composta. No código acima, exemplificado em Delphi, poderÃamos separar as partes da instrução SQL com o comando Add
da Query:
1 2 3 4 5 6 7 8 9 |
Query.SQL.Add('Insert into CLIENTES'); // Tabela Query.SQL.Add('(Codigo, Nome, CPF, DataNasc, Cidade)'); // Campos da inserção Query.SQL.Add('values ('); Query.SQL.Add(StrToInt(EditCodigo.Text) + ','); // Campo "Código" Query.SQL.Add(QuotedStr(EditNome.Text) + ','); // Campo "Nome" Query.SQL.Add(QuotedStr(EditCPF.Text) + ','); // Campo "CPF" Query.SQL.Add(StrToDate(EditDataNasc.Text) + ','); // Campo "DataNasc" Query.SQL.Add(QuotedStr(EditCidade.Text)); // Campo "Cidade" Query.SQL.Add(')'); |
Embora uma única linha se transforme em várias, a interpretação do código fica bem mais nÃtida.
Porém, em alguns casos, o caracter “+” é a única alternativa para concatenar um texto, como a montagem de um filtro de consulta composta. Mesmo assim, ainda podemos dividir as concatenações em linhas:
1 2 3 4 |
sFiltro := 'Nome like ' + QuotedStr(EditNome.Text) + '%'; sFiltro := sFiltro + ' and Cidade = ' + QuotedStr(EditCidade.Text); sFiltro := sFiltro + ' and Profissao = ' + QuotedStr(EditProfissao.Text); sFiltro := sFiltro + ' and Sexo = ' + QuotedStr(EditSexo.Text); |
Para mensagens, a concatenação fica ainda mais fácil. O Delphi disponibiliza o comando Format
para gerar uma string substituindo parâmetros por valores:
1 2 3 4 |
sMensagem := Format('A venda nº %d foi registrada para o cliente %s.', [StrToInt(EditCodigo.Text), EditNome.Text]); ShowMessage(sMensagem); |
Moleza, né?
Surgiu alguma dúvida sobre os tópicos do artigo? Gostaria de complementar algo? Deixe um comentário! 🙂
Hoje fico por aqui, pessoal. Obrigado e até a próxima!
Bom dia, André.
Já cometi esse erro de usar valores literais em vez de constantes ou ‘variáveis’. Há poucos dias tive um problema causado por isso, (if UF=”SP” then…) e se eu tivesse usado uma constante(UF_EMISSAO), teria poupado horas procurando as ocorrências em 3 programas (não imaginava vender fora de ‘SP’).
Concordo com você, Aquele “desenvolvedor” que você citou está completamente errado. É muito melhor e mais legÃvel usar uma constante ou variável, com um nome que identifica o conteúdo. Na minha opinião, o único tipo de aplicação que tem constantes imutáveis são de engenharia e cientÃficas (PI, seno, cosseno, fatores de conversão, e outras regras). Para quem lida com elas no dia a dia, são fáceis de reconhecer, mas se a aplicação precisar mudar a precisão de “PI” (‘3,1415’ 4 decimais para ‘3,14159 26535’ 10 decimais) vai ter que procurar todas as referências e corrigir. Uma constante resolveria isso fácil. Eu uso variáveis com essa função, mas poderiam ser constantes, sendo que sempre carrego no inicio do programa como parâmetros. Acho que este tipo de código (“eu entendo, está bom”) é como você comentou em no artigo “Evite a propriedade do código (Code Ownership)”. O problema é pra quem tiver que alterar ou corrigir.
No caso das “Query’s” e também filtros, faço exatamente como vc orientou, muito mais fácil e claro.
Mais uma vez obrigado pela Orientação.
Abraço.
Olá, Gerson, como vai?
Sem dúvidas, a utilização de constantes no projeto agiliza, e muito, a atividade de manutenção, além da questão da expressividade do código. Quanto mais o código poder “falar por si só”, melhor será a compreensão.
Tem razão, Gerson, propriedades cientÃficas e matemáticas possuem valores imutáveis mas, mesmo assim, ainda recomendo a utilização de constantes, já que nem todos os desenvolvedores têm conhecimento dos valores dessas propriedades. Por exemplo, ler a constante nVALOR_PI é mais nÃtido do que o número literal “3.14”.
Obrigado novamente pelo comentário!
Ola, André.
Como eu disse, apoio o uso de constantes, e que o nome descreva o conteúdo. Mas qual a diferença em declarar ela como constante ou variável?
Uso variáveis, que na verdade são constantes, pois só atribuo um valor na inicialização do programa (normalmente são parâmetros de configuração), raramente mudam, nunca durante a execução.
Posso acrescentar que comentar o código também é boa pratica, principalmente procedures e funções. Às vezes me deparo com procedimentos que criei há 15 anos e penso: “O que isto está fazendo aqui. Pra que serve?”, então procuro fazer um comentário dentro dela e também procuro colocar um nome que identifique a sua utilidade, como “alinha_a_direita”, “centraliza” e “retira_acentos”. Ajuda bastante a depuração e entendimento do código.
Bom dia, Gerson.
A maior diferença entre variáveis e constantes são que essas últimas não permitem que o valor seja modificado.
Já me deparei com situações em que valores de variáveis foram indevidamente alterados durante a execução da aplicação, gerando inconsistências. Ao trocar essas instâncias por constantes, o problema foi solucionado.
Além disso, o uso de constantes também expressa que o valor é fixo, e não variável. Em um ambiente colaborativo de desenvolvimento, essa informação pode ajudar a compreender melhor o código.
No Delphi, constantes podem ser declaradas com a palavra const.
UM ORM não seria o mais indicado ? sim pois seria muito mais interessante e seguro que minha string sql refletisse exatamente os campos das minhas entidades…
Lindemberg, a instrução SQL no artigo foi utilizada somente como exemplo.
Mesmo assim, sim, ORM é uma boa alternativa para persistência de dados e poderia substituir o primeiro exemplo da concatenação composta.
Abraço.
Parabéns pelo site gostei muito. 😉
Obrigado, Maria!