Como falei no post anterior sobre preocupações com determinados detalhes quando nomear variáveis, vamos agora trazer algumas polêmicas e dar algumas direções sobre concisão de nomenclatura das variáveis.
Ao contrário do Clean Code…
Diferente do que propõe o Clean Code, do Uncle Bob, que recomenda o uso de prefixos como is
ou has
para variáveis booleanas — como em isEffective
em vez de simplesmente effective
.
private boolean isEffective = false;
if (isEffective) {
// ...
}
Segundo o autor, o uso desses prefixos deixa mais evidente que se trata de uma variável booleana, evitando confusão com objetos ou outros tipos e indique claramente um valor verdadeiro ou falso.
Minha visão: clareza pela concisão
Eu penso um pouco diferente. Meu foco está em escrever menos e reduzir o esforço cognitivo. Se estou declarando uma variável booleana, é porque pretendo avaliá-la em uma estrutura de controle, como um if
. E dentro de expressões condicionais, por definição, só lidamos com booleanos.
Por isso, prefiro algo mais direto:
private boolean effective = true;
if (effective) {
//...
}
O uso da variável no código fica muito mais limpo. Sei que ela é um booleano porque, dentro de uma expressão condicional, apenas valores booleanos são avaliados. Seja em um while
ou em um if
, no fim, estamos lidando com resultados de expressões booleanas.
Se faço algo como 2 > 3
, o resultado é false
. Se substituo os números por variáveis, continuo lidando com uma expressão booleana. Simples assim. Além disso, se essa variável estiver declarada em uma classe ou objeto, a própria IDE já indica o tipo, como faria com qualquer outro valor.
Agora, há um caso que foge a essa regra: quando a variável não é um booleano direto, mas sim uma função que retorna um booleano. Em linguagens como JavaScript, Kotlin e Haskell, funções e lambdas podem ser tratadas como variáveis, sendo até passadas como parâmetros para outras funções.
Passar uma função como parâmetro? Pois é, para quem nunca viu isso, pode parecer estranho no começo!
Isso nos leva a alguns conceitos fundamentais da programação funcional. Além dos tradicionais map
, filter
e reduce
, dois conceitos particularmente relevantes são Currying e Higher-Order Functions (HoF). Currying transforma uma função com múltiplos parâmetros em uma cadeia de funções que recebem um único argumento por vez, permitindo maior flexibilidade na composição de funções. Já Higher-Order Functions são funções que aceitam outras funções como argumento ou retornam uma função como resultado, tornando o código mais modular e reutilizável. Parece complexo? Podemos explorar isso melhor em outro momento, caso queira se aprofundar.
Voltando ao ponto: quando declaro uma função que retorna um booleano, aí sim faz sentido usar o prefixo is
, pois ele segue a convenção típica de métodos em linguagens orientadas a objetos, como get
, set
, is
e has
.
Nesse caso, a variável não representa mais um valor direto, mas sim uma chamada de função, tornando a nomeação com prefixo mais intuitiva e coerente com o comportamento esperado.
um exemplo simplório de Currying:
// Função curried para comparar se um número é maior que um valor esperado
const isGreaterThan = (expected) => (value) => value > expected;
// Cria uma função específica: verifica se é maior que 10
const isGreaterThanTen = isGreaterThan(10);
if (isGreaterThanTen(15)) {
console.log("O valor é maior que 10.");
} else {
console.log("O valor é 10 ou menor.");
}
Lembra do que falei sobre convenções? Se você estiver seguindo um padrão de desenvolvimento baseado em Fluent Interface, então o uso de prefixos como is
, has
, get
pode (e até deve) ser suprimido em nome da legibilidade fluente.
Nesse estilo, o código se torna mais natural de ler, como se fosse uma frase. Por exemplo:
order.isPaid(); // convencional
order.paid(); // estilo fluent
Aqui, não existe certo ou errado, é uma questão de preferência e consistência. Mas se você optar por seguir esse modelo mais fluido, é fundamental discutir com o time e formalizar isso como um padrão adotado coletivamente.
Essas pequenas decisões, quando padronizadas, evitam retrabalho, discussões desnecessárias em PRs e aumentam a fluidez do desenvolvimento.
Ah, e reforçando: comento mais sobre combinar padronizações com o time neste artigo.
Uso de nomes diferentes para a mesma coisa
Lembra do que falei no post anterior sobre padrões? Ter consistência na nomenclatura é essencial dentro de um time. Um dos erros mais comuns e perigosos é usar nomes diferentes para representar o mesmo conceito ao longo do código. Isso torna tudo mais confuso, reduz a confiança de quem está fazendo alterações ou correções, aumenta o risco de introduzir erros por pura inconsistência e compromete diretamente a legibilidade, a manutenibilidade e a compreensão geral do sistema.
Imagine um código onde a variável que representa um usuário é chamada de user
em uma função, client
em outra e accountHolder
em uma terceira. Embora tecnicamente possam se referir à mesma entidade, essa variação obriga quem está lendo a fazer um esforço mental extra para entender se são coisas diferentes ou apenas nomes distintos para o mesmo conceito. E esse tipo de dúvida quebra o fluxo de leitura, atrasa o entendimento e gera incertezas.
Essa falta de uniformidade nos nomes pode facilmente levar à duplicação de lógica, falhas de integração e até bugs de negócio. E o pior: tudo isso pode acontecer porque desenvolvedores assumem, com base apenas no nome, que estão lidando com entidades distintas, quando na verdade estão apenas diante de uma inconsistência semântica.
A confusão da negação
Um dos desafios comuns ao escrever código está no uso da negação em expressões condicionais. Vamos explorar esse ponto mais a fundo quando falarmos sobre código minimalista em funções, mas adianto um fato comprovado: o cérebro humano tem dificuldade em processar negações. Existem estudos desde 1975 que investigam isso.
Pense comigo: quando digo “o cérebro não entende o ‘não’ direito”, percebe como seu raciocínio fica ligeiramente mais lento e você voltou pelo menos duas vezes para entender? A negação adiciona uma camada extra de complexidade ao pensamento linguístico. Se a frase envolve múltiplas negações, interpretar a intenção torna-se ainda mais difícil. Alguns estudos indicam que o cérebro primeiro processa a frase como afirmativa e só depois aplica a negação. Outros apontam que frases negativas exigem significativamente mais tempo para serem compreendidas em comparação com frases positivas. Ou seja, além da ambiguidade natural da linguagem, há fatores que ampliam a complexidade cognitiva.
Agora observe este exemplo:
boolean notFound = false;
if (!notFound) {
System.out.println("Item encontrado.");
} else {
System.out.println("Item não encontrado.");
}
À primeira vista, notFound
parece uma variável clara. Mas perceba o que acontece ao negar uma variável já negativa. Mesmo em um exemplo simples como esse, a leitura se torna menos intuitiva e exige um esforço extra para interpretação.
E aqui estamos lidando com uma única variável. Agora imagine expressões compostas, com múltiplas condições negadas e encadeadas. A dificuldade de leitura e compreensão cresce exponencialmente.
Isso ocorre frequentemente porque os requisitos costumam vir escritos com negação. Algo como “em casos de exceção, o valor não pode ser A, B ou C”. O desenvolvedor, especialmente em um estágio menos maduro da carreira, tende a transcrever essa lógica exatamente como descrita. Se o card diz “Se for A, faça isso. Se for B, faça aquilo. Caso contrário, lance um erro”, ele vai seguir à risca o texto, sem avaliar maneiras mais eficazes de estruturar a lógica ou aplicar padrões de design para tornar o código mais legível.
Imagina um user story assim:
“Como usuário, quero garantir que o sistema não ignore entradas que não sejam inválidas, para que nem dados incorretos nem corretos sejam descartados por engano.”
Escrever bem é uma habilidade, e a linguagem pode tanto possibilitar quanto limitar a comunicação. Só conseguimos nos expressar dentro dos limites do que conhecemos da linguagem, que, por si só, já carrega ambiguidade. Por isso, ter um bom vocabulário e a capacidade de produzir um texto claro e compreensível para os outros é essencial para uma comunicação eficaz.
“O maior problema com a comunicação é a ilusão de que ela aconteceu.” — William H. Whyte
E aqui vale um reforço importante: imaturidade não tem relação com tempo de carreira. Maturidade está ligada à capacidade de entender melhor os problemas e fazer boas escolhas de design. Refletir sobre o impacto da negação na leitura do código é um desses sinais.
Então um ponto essencial para tornar o código mais compreensível é evitar variáveis que indicam negação. Embora possa parecer natural declarar uma variável booleana com um nome negativo, isso pode dificultar a interpretação do código. O ideal é transformar essa lógica em uma afirmação clara, eliminando a necessidade de interpretar a negação.
var off = true;
if (off) {
System.out.println("Desligado");
} else {
System.out.println("Ligado");
}
O código funciona, mas a leitura não é tão intuitiva. Quando alguém analisa essa lógica, precisa primeiro interpretar que off
significa “desligado”, depois confirmar que sua negação é verdadeira, gerando um esforço cognitivo desnecessário. Agora, compare com esta versão:
var on = true;
if (on) {
System.out.println("Ligado");
} else {
System.out.println("Desligado");
}
Aqui, a interpretação flui de maneira natural: se on
for verdadeiro, o dispositivo está ligado; se for falso, está desligado. Não há necessidade de processar mentalmente a inversão.
“Qualquer tolo pode escrever código que um computador entende. Bons programadores escrevem código que humanos entendem.” – Martin Fowler
Lembre-se: o objetivo não é apenas garantir que o código funcione, mas que outros desenvolvedores possam compreendê-lo com o mínimo de esforço. Um código claro e direto reduz a chance de erros e melhora a manutenção. Quanto mais intuitivo ele for, menos tempo e energia serão gastos interpretando sua lógica.
A importância de nomes semânticos
Outro dia, eu estava ajudando um desenvolvedor com um problema e, ao analisar o código, me deparei com algo como #isStatus()
. Comentei na hora: “Nossa, isso quebrou qualquer expectativa que eu poderia ter com um atributo chamado status
.” Curioso, né? Mesmo seguindo algumas práticas do Clean Code, o uso de isStatus
como nome de uma variável booleana gerou um problema sério: o nome sugere uma coisa, mas representa outra completamente diferente.
Quando vejo status
em um objeto, minha expectativa imediata é que seja um enum
, uma string
ou, no pior dos casos, como ainda é comum em sistemas legados, um int
. Mas ali, era simplesmente um boolean
. Isso quebra a semântica esperada, dificulta o entendimento e gera confusão, mesmo em situações simples.
Outro exemplo clássico é uma variável nomeada como data
. Em um ponto do código, ela representa uma data de nascimento. Em outro, é um objeto genérico vindo de uma API. Essa reutilização compromete a clareza semântica e força o leitor a constantemente retomar o contexto para entender do que se trata aquela variável. Isso reduz a legibilidade e aumenta o esforço cognitivo de quem está lendo ou mantendo o código.
Escolher nomes inadequados quebra uma das premissas fundamentais das boas práticas de programação: criar nomes descritivos, precisos e consistentes com a responsabilidade da variável.
Evitar esse tipo de erro exige atenção ao domínio do problema, clareza sobre o papel de cada variável e um olhar cuidadoso para manter a consistência terminológica ao longo do sistema. Ferramentas de análise estática e o processo de code review podem ajudar a identificar esses casos, mas, no fim das contas, a maior responsabilidade continua sendo do bom senso e da disciplina do desenvolvedor.
Conclusão
Uma nomenclatura clara e consistente é essencial no desenvolvimento de software, impactando diretamente a legibilidade, manutenção e eficiência da equipe. O uso inadequado de nomes, como prefixos confusos ou termos ambíguos, pode dificultar a compreensão do código e levar a erros. Seguir padrões de nomenclatura bem definidos dentro do time cria um ambiente de desenvolvimento mais organizado e reduz a carga cognitiva dos programadores. Além disso, a maturidade na programação não se mede pelo tempo de carreira, mas pela capacidade de tomar decisões inteligentes de design e aplicar boas práticas. Em um ambiente colaborativo, escolher nomes apropriados é fundamental para garantir que todos compreendam o sistema e trabalhem de forma eficiente.