Minimalist Code – Declarando variáveis do jeito certo

aprendendo a declarar variáveis

Em um post anterior, discuti o impacto dos nomes de variáveis, destacando a importância de se declarar bons nomes, pois isso reflete diretamente na qualidade do código. Essa prática é resultado de profissionalismo como engenheiro de software e impacta diretamente na legibilidade, manutenção do código e no desempenho tanto profissional que o escreve quanto da equipe como um todo.

No entanto, é importante destacar que o tempo de experiência na área não é sinônimo de competência. Infelizmente, existem muitos profissionais com anos de estrada que ainda atuam de forma amadora, com um nível de desleixo que compromete a qualidade das soluções. Em muitos casos, essas soluções acabam sendo criadas para resolver um único problema, mas acabam englobando múltiplos outros problemas não resolvidos de forma eficiente.

Isso, entretanto, não significa que um bom profissional esteja imune a cometer erros. Pelo contrário: um engenheiro de software experiente busca feedbacks constantes, com uma mentalidade de falhar rapidamente e corrigir de maneira ágil. O ponto que estou destacando aqui é o comportamento dos profissionais inexperientes, amador ou desleixados, que infelizmente estão presentes em qualquer campo de atuação.

Vibe coding

O “vibe coding” é um movimento que provavelmente trará um aumento nas responsabilidades dos engenheiros de software mais experientes. Isso ocorrerá porque, além de revisar o código de outros desenvolvedores, será necessário também revisar o código gerado por ferramentas de IA, que, no futuro, podem ser produzidas por equipes fora do time de tecnologia, como os times comercial, de marketing, entre outros.

Atualmente, essas ferramentas de IA são eficazes como apoio, mas ainda não têm a maturidade necessária para substituir completamente um profissional de tecnologia. Elas podem ser extremamente úteis para acelerar processos ou auxiliar em tarefas específicas, mas não devem ser encaradas como a ferramenta principal para desenvolvimento de software. A revisão crítica e a capacidade de entender a fundo o contexto do problema são habilidades que, por enquanto, continuam sendo exclusivas dos profissionais experientes.

Além disso, há um ponto importante: habilidades que não são constantemente requeridas acabam se deteriorando com o tempo. Quando não são usadas, essas habilidades perdem a acuidade, o que pode impactar negativamente a qualidade do trabalho. Em um cenário em que ferramentas como IA estão em crescimento, mas ainda dependem da supervisão e intervenção humana, é essencial que os engenheiros de software mantenham suas habilidades afiadas e atualizadas, para garantir que a evolução tecnológica não seja uma ameaça, mas sim uma oportunidade de crescimento.

“Somos o que fazemos repetidamente. Excelência, então, não é um ato, mas um hábito.” – Aristóteles

Escolhas a partir do ambiente

Grande parte das orientações que vou compartilhar são direcionadas a um ambiente corporativo, onde o foco não está em otimizar recursos de máquina devido a restrições técnicas, como em dispositivos IoT, ou em otimizações voltadas para objetivos específicos. Como já mencionei em outros posts: não existem soluções únicas, mas sim trade-offs. Portanto, essas orientações se aplicam a projetos de grande escala, nos quais há mudanças frequentes tanto no negócio quanto nas equipes envolvidas.

Nesse contexto, algumas das principais qualidades que essas práticas visam promover incluem: legibilidade (readability), modularidade (modularity), capacidade de mudança (changeability) e manutenibilidade (maintainability). Esses requisitos de qualidade são fundamentais para garantir que o código permaneça sustentável e adaptável ao longo do tempo.

Não use comentários

Um bom código é a sua própria documentação, e acho que essa frase já deixa clara a intenção. Tornar-se proficiente em escrever código de forma clara e eficiente é uma habilidade fundamental para um bom engenheiro de software. De fato, aprender a codificar bem é tão importante quanto entender arquitetura, padrões de projeto e boas práticas. Escrever de forma simples e direta, de modo que o próprio código se torne sua documentação, facilita a legibilidade e, consequentemente, a manutenibilidade.

“Comentários são uma solução temporária para um código mal escrito. Ao invés de escrever um comentário, escreva código melhor.” – Martin Fowler

Comentários, além de aumentarem o número de linhas do arquivo, criam um esforço extra para serem ignorados e você inevitavelmente irá ignorá-los. Eles também indicam que o código precisa de explicações adicionais para ser compreendido. A única exceção seria quando é necessário gerar documentação a partir dos comentários, como no caso de um JavaDoc para uma biblioteca. No entanto, no mundo atual de APIs e MCPs, essa prática está se tornando cada vez menos necessária.

Vale lembrar que linguagens de alto nível são projetadas para serem mais próximas da linguagem humana. Isso significa que o código deve ser voltado para pessoas, e essas linguagens evoluíram para se tornarem cada vez mais simples e enxutas, como Swift e Kotlin. Contudo, elas não resolvem todas as ambiguidades da linguagem humana. Por isso, escrever um bom código é uma habilidade essencial para um engenheiro de software, assim como um escritor tem a habilidade de criar uma obra literária.

Nomes vagos, genéricos ou sem contexto

int data = 10; // o que é 'data'? pode ser qualquer coisa

Nomes abreviados ou sem contexto diminuem drasticamente a capacidade de entendimento, aumentando o esforço cognitivo para descobrir o que a variável representa, como ela deve ser preenchida, se é opcional ou não, e quais os efeitos de mudanças de estado podem ocorrer ao ser modificada.

// o que é 'person data'? pode ser qualquer coisa
PersonData personData = new PersonData();

// o que é 'person info'? pode ser qualquer coisa
PersonInfo personInfo = new PersonInfo();

// qual objeto representa 'o' 
// e para o negócio o que info representa como atributo ou comportamento?
o.getInfo();

Esses objetos acabam se tornando uma verdadeira “sacola” de variáveis. O desenvolvedor, ao não entender profundamente o problema ou o domínio do negócio, cria objetos genéricos que não refletem claramente o que representam no contexto específico da aplicação. Isso ocorre porque, muitas vezes, o desenvolvedor não se aprofunda o suficiente no entendimento do problema e trata as variáveis de maneira superficial, sem se preocupar em construir uma modelagem eficaz.

Esse problema decorre da falta de compreensão do negócio ou, muitas vezes, da falta de maturidade profissional. Quando falo sobre o entendimento do negócio ou modelagem de negócio, refiro-me ao conhecimento do especialista: aquela pessoa da linha de frente que estudou o domínio, compreende os processos, domina a linguagem específica do negócio e conhece os termos técnicos usados nesse contexto.

Erik Evans, em seu livro Domain-Driven Design, destaca a importância de se aproximar do especialista de negócio e entender como ele pensa e fala. Este especialista é quem realmente entende e vivencia o funcionamento do negócio no mundo real.

“A Ubiquitous Language é uma linguagem compartilhada por todos os membros da equipe, desenvolvedores, especialistas em domínio e outras partes interessadas, que trabalham juntos no projeto. Ela deve ser usada em todas as comunicações, tanto escritas quanto faladas, para garantir que todos tenham um entendimento comum do domínio.” – Eric Evans, autor de Domain-Driven Design: Tackling Complexity in the Heart of Software (2004)

Para que a linguagem ubíqua seja eficaz, ela deve se refletir nas interações entre o time técnico e o negócio, na nomeação de times, tribos (ou qualquer outra estrutura organizacional), bem como na definição dos nomes de projetos, pacotes, classes e variáveis.

“A linguagem ubíqua conecta os modelos de domínio com o código, garantindo que cada conceito tenha uma expressão no software e que a conversa sobre o software se concentre em aspectos do negócio em vez de detalhes técnicos.” – Vaughn Vernon, autor de Implementing Domain-Driven Design (2013)

Outra crítica que deixo é que os desenvolvedores estão cada vez mais distantes dos especialistas de negócio, algo que os signatários do Manifesto Ágil enfatizavam. Nos modelos modernos, geralmente surge um papel intermediário para atuar entre o especialista de negócio e o time técnico. No entanto, estamos enfrentando um problema de escassez de profissionais capazes de entender e traduzir essas regras de negócio de forma eficiente para o time técnico. Com frequência, as informações que chegam são imprecisas ou insuficientes, o que impacta diretamente no processo de refinamento. Esse impacto se reflete na solução final e, consequentemente, no código gerado.

Nomes acompanham intenção

É importante observar alguns detalhes de nomenclatura. Por exemplo, quando declaramos uma variável primitiva, o nome dela costuma ser no singular. Já no caso de coleções, como listas ou arrays, adotamos o plural.

var pessoa = new Pessoa(/*...*/);
var pessoas = new ArrayList<Pessoa>();

Apenas variáveis úteis

Variáveis não utilizadas devem ser excluídas do código, assim como importações desnecessárias. O Extreme Programming (XP) enfatiza a refatoração contínua como uma prática essencial para garantir a qualidade e a simplicidade do software. Durante o processo de refatoração, é crucial que apenas o código necessário e testado permaneça, eliminando qualquer parte que não agregue valor ou que não seja mais utilizada — evitando o que é comumente chamado de “código morto”.

Além disso, o XP reforça o princípio da simplicidade, que defende a ideia de escrever apenas o código estritamente necessário para resolver o problema em questão. Isso inclui a eliminação de qualquer trecho que não seja utilizado ou que não contribua diretamente para o valor do software, resultando em soluções mais claras, eficientes e fáceis de manter.

“Simplicidade é a arte de remover o que não é necessário. A complexidade adicionada desnecessariamente ao código só cria problemas, e não soluções.” — Kent Beck, autor de Extreme Programming Explained (1999)

Outro princípio que reforça essa ideia é o YAGNI (You Aren’t Gonna Need It). Ele nos lembra que devemos focar apenas no que é estritamente necessário, mantendo o código simples e direto. Ou seja, não devemos implementar pedaços de código com base na suposição de que iremos precisar deles no futuro. A intenção é usar bem o tempo de desenvolvimento, reduzindo o risco de investir esforço em algo que talvez nunca venha a ser útil. Isso contribui para um código mais claro, enxuto e livre de suposições — que, muitas vezes, acabam sendo equivocadas.

É importante destacar que isso não significa que devemos ignorar a necessidade de criar um design pensando em extensibilidade futura. A extensibilidade deve ser considerada quando há um problema conhecido, planejado e com previsão de implementação. Isso é bem diferente de adotar uma abordagem de overengineering de forma prematura. Em outras palavras, devemos criar um design com abstrações que suportem extensibilidade quando necessário, mas evitar implementações sem previsibilidade real de uso.

O ponto aqui é: não devemos criar implementações sem previsibilidade real de uso, seja no momento atual ou em um futuro próximo. Fazer isso, muitas vezes, é movido por ego do desenvolvedor e acaba gerando complexidade técnica desnecessária. Embora pareça uma “boa ideia”, esse tipo de decisão pode impactar negativamente o projeto, dificultando a compreensão por parte de outros desenvolvedores e adicionando peso à manutenção.

Voltando às variáveis, um exemplo simplório para ilustrar: imagine que você precisa criar uma máscara para um LocalDate.

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
// ...
public String format(LocalDate date) {
    return date.format(formatter);
}

Então, você pensa: “Já que estou criando uma máscara para LocalDate, vou deixar pronta também para a classe LocalDateTime.”

DateTimeFormatter localDateTimeFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy'T'HH:mm");
DateTimeFormatter localDateFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");

// ...

public String format(LocalDate date) {
    return date.format(localDateFormatter);
}
// ...

Você pode até pensar: “Não estou precisando disso para a atividade atual, mas já que estou criando o formatador para LocalDate, é melhor deixar pronto também para LocalDateTime”.

No entanto, ao fazer isso, você acabou quebrando esse princípio. Pode ser que esse formatador nunca venha a ser usado, e você já está poluindo o código com partes desnecessárias.

Outro princípio relacionado é o KISS (Keep It Simple, Stupid!), que propõe manter o código simples e organizado. Isso significa criar e utilizar padrões quando necessário, mas evitar variáveis, implementações ou complexidades que não agregam valor real ao software.

Declarar apenas quando necessário

Antigamente, os compiladores eram bem menos sofisticados do que os que temos hoje, e uma estratégia comum era declarar as variáveis logo no início. Isso permitia ao compilador realizar otimizações mais simples e eficientes, como alocar memória de forma contígua e mais previsível. Além disso, ajudava o programador a ter uma visão clara das variáveis que seriam usadas no método. Linguagens como C e Fortran eram exemplos onde essa prática era muito comum, vindo de um modelo de programação mais tradicional e imperativo.

No entanto, essa abordagem apresentava problemas. Além do programa poder entrar em um estado incorreto por usar uma variável em um escopo errado, havia também o risco de memory leak, quando o desenvolvedor esquece de desalocar uma variável que não está mais sendo utilizada.

Hoje em dia, essa prática não é mais recomendada. Em situações complexas, tentar lembrar qual o tipo da variável, quando ou como ela foi inicializada pode tornar o código difícil de entender. Para garantir uma leitura mais fluida e minimalista, é interessante declarar as variáveis apenas quando elas realmente precisarem ser inicializadas, pois isso aumenta a clareza e diminui os riscos de bugs relacionados ao uso indevido das variáveis.

// evite esse tipo de código
public Transaction process(...) {
	  LocalDate date;
	  Transaction transaction;
	  
	  // ...
	  
    date = LocalDate.now();
    transaction = new Transaction(..., date);
		transaction = transactionService.save(transaction)
    
    return transaction;
}

// essa seria a recomendação
public Transaction process(...) {
    // ...
    var date = LocalDate.now();
    var transaction = new Transaction(..., date);
    
		return transactionService.save(transaction)
}

Isso diminui a quantidade de linhas em um método, reduz o esforço de leitura, torna o código mais fluido, evita falhas de inicialização em códigos mais longos e também elimina a “sujeira” no código, como variáveis não utilizadas que podem ser deixadas para trás devido a mudanças de lógica ou refatoração.

“Software é uma ótima combinação entre arte e engenharia.” — Bill Gates

Outro ponto a ser evitado é a declaração de variáveis em escopos diferentes de onde elas são realmente utilizadas. Por exemplo, uma variável que deveria estar dentro de um método, mas que é declarada fora dele, retendo memória por mais tempo do que o necessário. Isso aumenta as chances de problemas relacionados ao gerenciamento de memória e ao uso desnecessário de recursos.

int soma = 0;  // Declaração da variável temporária fora do método
public void exemplo() {
    for (int i = 0; i < 10; i++) {
        soma += i;
    }

    if (soma > 10) {
        System.out.println("Soma é maior que 10");
    }
}

// essa seria o correto
public void exemplo() {
    int soma = 0;  // Declaração da variável no menor escopo possível
    for (int i = 0; i < 10; i++) {
        soma += i;
        
        if (soma > 10) {
            System.out.println("Soma é maior que 10");
        }
    }
}

Estabeleça convenções

É fundamental estabelecer convenções e padrões de arquitetura e escrita em um projeto. Uma padronização importante é a de nomenclatura de variáveis.

Em um projeto no qual trabalhei, que era relativamente novo (e reforço que “código legado” não significa necessariamente um código ruim), a solução em si resolvia um problema de forma bem interessante. No entanto, a execução em código não foi das mais pragmáticas. Enquanto tentava entender o código, enfrentei diversas dificuldades, muitas delas causadas por falhas grosseiras. Vou tentar ilustrar um dos problemas encontrados:

data class Transaction(
   val transactionId: UUID,
   ... // outras variáveis e
   val idTransaction: UUID
)

Essa classe tinha dois IDs. Até aí, tudo bem, mas um representava a identidade da própria classe, enquanto o outro se relacionava com o ID de outro sistema. O esforço para descobrir qual era qual se tornou um desafio. Inicialmente, os desenvolvedores do time não sabiam explicar o motivo de termos duas variáveis, e a solução encontrada para entender qual deveria ser usada era fazer uma busca para ver onde essas variáveis eram usadas ou inicializadas.

Não é algo complicado de investigar depois que você entende, mas se torna um esforço extra. Eu precisei parar alguém para me explicar, investigar o uso das variáveis e só então me concentrar na solução, o que gerou uma mudança de contexto e interrompeu o flow do trabalho. Isso dificulta o entendimento do sistema, aumenta as chances de erro e diminui a produtividade de quem está codando, além de afetar a performance do time como um todo.

No Extreme Programming (XP), entre as dez práticas, a nona é a prática da codificação de padrões. O uso de convenções e padrões de codificação é essencial para garantir que o código seja claro e facilmente compreendido por todos os membros da equipe.

Outro exemplo de convenção é o formato para nomeação de variáveis. No mundo .NET, é amplamente convencionado o uso do PascalCase, onde todas as iniciais são maiúsculas. Já em linguagens como Java e Kotlin, a convenção é o camelCase, em que a primeira letra é minúscula e a primeira letra das palavras subsequentes são maiúsculas.

Entender essas convenções gerais é importante, pois, por exemplo, em GoLang, quando declaramos uma variável pública, ela deve começar com letra maiúscula, enquanto uma variável privada deve começar com letra minúscula. Essas convenções são parte da própria linguagem e ajudam a manter a consistência no código.

// Golang
package main

import "fmt"

type Pessoa struct {
    Nome string  // Pública, pois começa com letra maiúscula
    idade int   // Privada, pois começa com letra minúscula
}

func (p *Pessoa) SetIdade(i int) {
    p.idade = i  // Privada, acessada dentro do pacote
}

func (p *Pessoa) GetIdade() int {
    return p.idade  // Privada, acessada dentro do pacote
}

func main() {
    p := Pessoa{"João", 30}
    fmt.Println(p.Nome)  // Acessando variável pública
    // fmt.Println(p.idade)  // Erro! Não pode acessar a variável privada diretamente

    p.SetIdade(31)
    fmt.Println(p.GetIdade())  // Acessando variável privada via método público
}

No JavaScript, existe uma convenção popular para indicar variáveis privadas: elas geralmente começam com um underscore (_). Isso serve como uma forma de sinalizar que a variável não deve ser acessada diretamente fora da classe ou módulo, embora, tecnicamente, o JavaScript não ofereça encapsulamento de maneira tão rígida quanto outras linguagens.

// JavaScript
class Pessoa {
  constructor(nome, idade) {
    this._nome = nome;  // Convenção para variável privada
    this._idade = idade;  // Convenção para variável privada
  }

  getNome() {
    return this._nome;  // Acessando a variável privada via método público
  }

  setNome(nome) {
    this._nome = nome;  // Alterando a variável privada via método público
  }

  getIdade() {
    return this._idade;
  }
}

const pessoa = new Pessoa("João", 30);
console.log(pessoa.getNome());  // Acessando via método público
pessoa.setNome("Carlos");
console.log(pessoa.getNome());  // Acessando via método público

Cuidado com os “sotaques”

Muitas vezes, um programador que vem de outra linguagem acaba trazendo consigo os “sotaques” dessa linguagem. Por isso, é importante se adequar não apenas às convenções da linguagem, mas também às convenções que o time e a empresa preestabelecem.

Por exemplo, caso um programador .NET construa um projeto em Java seguindo as convenções que aprendeu em sua “linguagem mãe”, ele pode adotar o modelo de nomeação PascalCase, fazer a quebra de linhas antes de abrir blocos, e outros padrões que são típicos do .NET. Quando um programador Java precisa dar continuidade ou fazer manutenção nesse código, é recomendável manter o modelo de convenções que já está sendo utilizado no projeto, para não causar uma bagunça de estilos. Eu sei, isso pode incomodar bastante, mas é importante manter o projeto coerente até o final.

Se o time decidir, por consenso, que as convenções precisam mudar, então o ideal é refatorar o projeto inteiro para a convenção padrão do Java, que é o camelCase. No entanto, isso nem sempre é viável, especialmente em projetos grandes ou com prazos apertados.

Vamos agora revisar algumas convenções básicas a seguir:

// nome precisa ser declarativo e sucinto
Pessoa pessoa = new Pessoa(...);

// o fato de estar no plural já indica que pessoas é uma collection
List<Pessoa> pessoas = new ArrayList<>();

// alguns gostam de usar prefixo *is, has* nas variáveis, eu prefiro usar o próprio nome
// muitas vezes já é auto explicativo
boolean ativo = false

// constantes devem ser sempre caixa alta no padrão snake case
public static final Integer VALOR_INICIAL = 10

// evite tipos genéricos em nomes de variáveis
List<String> stringList;

Suprimir sufixos e prefixos redundantes

Outro problema comum é o aumento da verbosidade nas declarações de variáveis. Lembre-se de que, para codar em alta performance, buscamos minimizar o esforço de leitura. E vou reforçar isso várias vezes ao longo do texto: o esforço excessivo na leitura do código gera impactos negativos na produtividade. A consequência desse excesso de verbosidade é que torna-se mais difícil modelar e estruturar um bom design, o que é uma das principais preocupações dos bons engenheiros de software.

Nem sempre pecar pelo excesso é algo positivo. Um exemplo disso é quando nomeamos variáveis e, ao criar seus nomes, acabamos gerando redundâncias desnecessárias.

Código limpo sempre parece ter sido escrito por alguém que se importa — Robert C. Martin

// Não precisa repetir o nome da classe ou do tipo na variável
public class Transaction {
        private UUID transactionId;
	private LocalDate transactionDate;
	private BigDecimal valueTransaction;
	
	private LocalDateTime createdDate;
}

Evite repetir o nome da classe ou do tipo no nome da variável. O nome da classe já está dentro de um contexto que define qual é, então repetir o nome da classe nos atributos não faz sentido. Por exemplo, na classe Transaction, todos os atributos já pertencem a essa classe, e repetir o nome da classe nos atributos apenas aumenta a verbosidade sem agregar valor à modelagem do objeto. Repetir o nome do tipo nas variáveis não as torna mais fáceis de entender. Para evitar isso, estabeleça padrões de nomenclatura para os tipos de atributos no projeto.

Agora, o que fazer quando atributos de uma classe A contêm IDs ou referências de outra classe B? Nesse caso, devemos usar o nome da outra classe no nome do atributo? Vou deixar essa reflexão para você e desenvolvê-la em outro post.

Então, um formato mais minimalista seria:

public class Transaction {
        private UUID id;
	private LocalDate date;
	private BigDecimal value;
	
	// aqui você convenciona que todo created deve ser um LocalDateTime
	private LocalDateTime created;
}

Quando digo que é importante estabelecer convenções, é porque o óbvio, muitas vezes, precisa ser dito. O que é óbvio para alguns, nem sempre é para outros. Por mais que você acredite que deveria ser algo natural, muitas vezes é preciso reforçar esses pontos. Portanto, documentar as convenções ou criar um onboarding que comunique claramente essas diretrizes, ou até mesmo oferecer coaching durante o pareamento em ambientes que promovem boas práticas, são ações essenciais. Elas garantem a continuidade da manutenção e evolução com qualidade dentro do time, além de promoverem uma linguagem comum entre todos os membros, já que o time começa a falar e pensar de forma semelhante ao escrever o código.

Outro benefício dessas convenções é o impacto direto no uso das variáveis. Você percebe que, com uma boa convenção, o código se torna mais fluido, com menos texto para ler, o que simplifica a compreensão, desde que as variáveis tenham nomes bem escolhidos.

// ...
if (transaction.getValue() > LIMIT_FRAUD_ANALISIS) {
   // ...
}
// ...

Evitar variáveis longas

Lembre-se de que nosso objetivo é reduzir o esforço cognitivo e aumentar a legibilidade. Uma variável muito longa significa mais palavras para ler e interpretar, o que aumenta a dificuldade de entender seu propósito. Veja como um nome de variável excessivamente longo pode ser cansativo e dificultar a compreensão de sua função:

// sem o uso de um padrão convencional
String issoeumtextograndeparamostrarcomopodeserdificilentender = "Certamente"

String mesmoPalavrasComoCamelCaseSeTornaComplicado = "Você percebeu?"

Além disso, é importante ter em mente que, se você usa ferramentas como Checkstyle, Lint e outras similares (e eu realmente recomendo seu uso), elas ajudam a validar estaticamente se o seu código segue os padrões recomendados pela comunidade, o que contribui para a melhoria da qualidade do código.

Para quem nunca usou essas ferramentas, elas também são ótimas para aprender boas práticas e desenvolver suas habilidades na escrita de código. No caso deste exemplo, se você passar uma variável com um nome excessivamente longo, provavelmente ultrapassará o limite de caracteres configurado nessas ferramentas, que alertam para a violação de boas práticas. Limitar a quantidade de caracteres por linha tem como objetivo melhorar a legibilidade, padronizar o código para todos os desenvolvedores do time, forçar descrições mais claras das variáveis, além de evitar o deslocamento horizontal no editor, especialmente em monitores que não são widescreen.

Evite tipos genéricos e ambíguos

Fornecer tipos claros no código ajuda a melhorar a intenção e a compreensão do que está sendo feito. Lembre-se: seu código deve ser sua documentação. A melhor forma de documentar um código é por meio de uma boa modelagem, aliada a uma boa escrita.

Fazer uma boa modelagem orientada a objetos ou funcional é importante, mas se você declarar variáveis com nomes vagos, compostos apenas por letras e números, você estará dificultando a leitura, o entendimento e, consequentemente, a manutenibilidade do código.

Portanto, evite, por exemplo, usar objetos que não possuam características claras e diretamente relacionadas ao negócio ou ao problema que você está resolvendo. Veja um exemplo:

Object pessoa = new Funcionario(...);
List<Object> pessoas = new ArrayList();

Entenda que, apesar do nome da variável deixar claro o que se espera daquela instância de objeto, por ser um tipo genérico, ela pode ser sobrescrita nas próximas linhas com qualquer outro tipo. Isso significa que a variável perde sua identidade e pode se tornar qualquer coisa, o que, em um código grande e complexo, aumenta as chances de erro. Portanto, devemos evitar esse tipo de prática.

Como podemos manter algo suficientemente genérico sem perder a essência do propósito? A solução é usar uma classe abstrata que, mesmo sendo genérica, limita os tipos que podem ser atribuídos a ela. No caso de coleções, por exemplo, podemos usar generics.

Esse tipo de abordagem também impacta na tipagem, enfraquecendo uma das grandes vantagens da tipagem forte, que é a verificação em tempo de compilação. Isso pode levar a erros em tempo de execução, além de quebrar o princípio de substituição de Liskov (Liskov Substitution Principle, ou LSP), que é um dos princípios fundamentais do design orientado a objetos.

O LSP afirma que objetos de uma classe derivada devem ser substituíveis por objetos da classe base sem alterar o comportamento esperado. Ao não garantir isso, corremos o risco de criar uma arquitetura onde o comportamento do sistema não é previsível ou seguro.

Pessoa pessoa = new Funcionario(...);
List<Pessoa> pessoas = new ArrayList();

Evite números mágicos

Vamos reforçar o mantra: devemos criar um código que seja fácil de ler e entender. Imagine que você está em um laço for e, do nada, há um cálculo no qual você pega um valor de uma variável e faz uma multiplicação com um número que aparece “do nada” no meio do código. Por mais que esse número pareça óbvio, ele interrompe o fluxo da leitura. Ao ler o código, você de repente precisa interpretar um número para entender seu significado, o que aumenta a complexidade cognitiva.

Para melhorar a legibilidade, é essencial substituir esses números por variáveis ou constantes com nomes descritivos. Isso transforma o código em algo mais claro e legível, onde tudo é facilmente interpretável como texto, e o significado de cada valor fica evidente. Dessa forma, o código se torna mais autoexplicativo e fácil de manter.

// perceba que tudo é texto e no meio do texto tem um número que você precisa interpretar
public double area(int raio) {
    return 3.14159 * raio * raio;
}

// perceba como ficou mais fluido na leitura um texto e deixa claro o propósito do método
public double area(int raio) {
    return PI * raio * raio;
}

Além disso, se o valor for reutilizado em outros lugares, considere movê-lo para uma variável em uma classe apropriada para centralizar o uso. Caso contrário, se o valor for específico para aquele contexto, torne-o uma constante privada dentro do escopo adequado.

Conclusão

Veja que falamos bastante apenas sobre variáveis e não esgotamos ainda o assunto sobre boas práticas em nomeação de variáveis, existe outras práticas que podemos desenvolver em outro post.

A qualidade do código não é determinada apenas pela funcionalidade que ele oferece, mas pela sua legibilidade, simplicidade e capacidade de manutenção ao longo do tempo. Um bom engenheiro de software sabe que um código bem escrito não precisa de explicações externas, pois ele mesmo se documenta através de boas práticas e escolhas de design.

Ao adotar convenções claras o desenvolvedor contribui diretamente para a criação de um ambiente de desenvolvimento mais eficiente e colaborativo. A manutenção e evolução do código tornam-se mais ágeis quando todos os membros do time estão alinhados com uma linguagem comum e uma estrutura compreensível.

Princípios como o KISS (Keep It Simple, Stupid!), o YAGNI (You Aren’t Gonna Need It), e o LSP (Liskov Substitution Principle), entre outros, devem ser observados para garantir que o código seja não apenas funcional, mas sustentável. O uso de ferramentas como linters e checkstyles pode ajudar a reforçar essas práticas, promovendo uma programação mais limpa e livre de redundâncias.

No final, um código bem escrito vai além de uma simples implementação de funcionalidades; ele reflete o profissionalismo, o domínio do problema e a capacidade de comunicar claramente soluções complexas de maneira simples e eficaz. Portanto, ao investir em boas práticas de codificação, não só melhoramos o produto final, mas também o ambiente e a performance do time como um todo.

No próximo artigo, vamos explorar alguns cenários que devem ser evitados para garantir que o código continue limpo, conciso e de fácil manutenção.