Código Minimalista: Como Nomear Variáveis Afetam Legibilidade e Performance
Hoje o papo vai ser técnico, embora à primeira vista não pareça tanto. O que podemos falar sobre variáveis? Seriam os tipos delas? O tamanho de cada tipo? Como são alocadas e onde ficam armazenadas? Como elas se comportam em um array ou objeto? Como as constantes são alocadas na memória? E as variáveis imutáveis, como funcionam? E sobre overflows? Existem ataques que acontecem por conta de variáveis? Como as operações com essas variáveis acontecem no processador? Como calcular o uso de espaço? como otimizar o uso de recurso computacional com um bom design?
Esses são assuntos que podemos abordar melhor em outros textos, porque são fundamentais para um bom desenvolvedor de software. Mas hoje vamos focar no básico, algo que muitas vezes é deixado de lado: como dar nomes às variáveis.
Você já parou para pensar sobre como escolhe os nomes das variáveis no seu código? Às vezes, essa escolha parece trivial e automática, mas ela pode fazer toda a diferença no longo prazo.
Essa é uma falha comum em muitos projetos em que trabalhei, sendo uma verdadeira ‘criptonita’ para muitos desenvolvedores. Dar nomes adequados às variáveis, que transmitam seu propósito de forma clara e fácil de entender, é essencial. Isso torna o código mais legível, melhora a modelagem e, consequentemente, contribui para uma entrega de melhor qualidade.
Já mencionei em outro post que um bom engenheiro de software deve sempre realizar duas entregas por tarefa: uma para atender às necessidades dos stakeholders e outra para seus colegas. Ou seja, o código no repositório precisa ser de qualidade, o que também contribui para um ambiente de trabalho saudável.
Mas, afinal, qual é a importância de simplesmente dar um nome a um espaço de memória?
“Todas as coisas são lícitas, mas nem todas convêm; todas as coisas são lícitas, mas nem todas edificam.” 1Coríntios 10:23 NAA — Paulo de Tarso
Reduzindo o esforço cognitivo com nomes de variáveis eficientes
Vamos esclarecer alguns pontos. Quando falamos sobre linguagens de programação e a declaração de variáveis, geralmente nos deparamos com dois paradigmas principais: as linguagens tipadas e as não tipadas. Não entrarei na discussão sobre qual das duas é melhor, pois, como em muitas situações, não existem soluções perfeitas, apenas trade-offs. O mais importante é compreender qual problema estamos dispostos a aceitar.
“Não há soluções, apenas trade-offs.” — Thomas Sowell
Você deve estar se perguntando: ‘Espera aí, Manoel, você começou falando sobre nomes de variáveis e agora está indo pra tipagem de linguagens?’ Calma! A ideia é conectar esses conceitos ao design de código e a um fator importante de qualidade: a readability. E, para ser sincero, tentar traduzir readability como ‘leitura’, ‘leiturabilidade’ ou ‘legibilidade’ não passa a real ideia do que o termo significa.
Quando trabalho com uma linguagem tipada, ao declarar uma variável, uma parte de sua assinatura envolve especificar seu tipo. Mas por que não podemos usar um número, uma letra ou um símbolo qualquer para declarar esse tipo? A resposta é simples: um bom design de código passa pela escolha de bons nomes, o que ajuda a reduzir o esforço cognitivo e aumenta a legibilidade do código. Lembre-se: passamos mais tempo lendo código do que escrevendo.
Algumas linguagens, como Kotlin, Golang e Python, são mais enxutas e exigem menos código para expressar o que queremos fazer. Já o Java, embora não seja o mais verboso, ainda exige um pouco mais de esforço na hora de escrever. A partir do Java 10, foi introduzida a palavra-chave var para simplificar a sintaxe e tornar o código mais conciso e legível. Com o var, ao obter uma variável, podemos omitir a declaração explícita do tipo, mas isso não significa que o tipo se torne mutável. No entanto, isso pode acabar comprometendo a clareza na leitura do código.
// Cenário hipotético de declaração de objeto complexo em Java (algo que já vi bastante)
Map<String, List<Pair<String, Object>>> variavel = o.getInfo();
// Introdução da palavra-chave **var** a partir do Java 10
var variavel = o.getInfo();
Ao adotar var, é possível perceber uma redução na verbosidade, mas isso vem acompanhado de um custo: a clareza na leitura do código. Uma boa maneira de mitigar esse impacto é dar nomes significativos às variáveis, permitindo que, mesmo sem a declaração explícita do tipo, o desenvolvedor consiga inferi-lo com facilidade. Porém, nem sempre isso será 100% claro.
Por exemplo, se você estiver lidando com um método chamado #getDate, é possível inferir que o retorno seja uma data, mas não saberemos com certeza se é java.util.Date
, java.time.LocalDate
, java.time.LocalDateTime
, ou até uma String
ou long
representando um timestamp. Como saber exatamente? Nesse caso, a única solução é realizar uma ação extra: entrar no método e conferir a assinatura do retorno. Algumas IDEs modernas já oferecem essa funcionalidade, mas, ao fazer isso, você precisa interromper o seu fluxo de leitura, mover o cursor do mouse e realizar alguma ação, o que causa uma mudança de contexto mental.
Então, a recomendação é não usar o var? Não, não é isso que estou dizendo. O que estou tentando mostrar é o impacto que uma decisão como essa pode ter. Pessoalmente, uso bastante o var. Ele economiza tempo de escrita e exige menos esforço para ler o código. Para mitigar a falta de clareza que pode surgir com o uso dele, estabeleço padrões globais de tipos no projeto e sempre busco dar bons nomes às variáveis.
Por exemplo, em meus projetos pessoais, prefiro usar ULID (Universally Unique Lexicographically Sortable Identifier) como padrão para os ids de entidades, pois ele oferece várias vantagens. ULID é lexicograficamente ordenável, facilitando a busca por registros em ordem temporal, e também pode ser gerado de maneira distribuída, sem risco de colisões. Além disso, o formato compacto e a facilidade de manipulação em bancos de dados tornam-no ideal para sistemas que exigem escalabilidade. Dependendo do projeto um timestamp posso estabelecer como padrão um LocalDateTime, ou pode ser um long. O importante é que uma vez definido um padrão, ele deve perpetuar por todo o projeto.
Se uma variável precisa passar por várias transformações, opto por criar um novo tipo para ela, encapsulando-a em um objeto. Isso evita o problema da obsessão com primitivos, outro desafio comum no design. Muitas vezes, o poder de usar objetos e encapsular dados de maneira adequada é subestimado. (Falo mais sobre isso aqui.)
O mesmo raciocínio se aplica aos nomes das variáveis. Eles devem ser significativos e expressar claramente o propósito dela, facilitando a leitura e a manutenção do código a longo prazo. Nomes autoexplicativos evitam ambiguidades e tornam o código mais intuitivo, tanto para quem escreve quanto para quem o revisa. Isso também reduz o risco de erros, pois torna o fluxo de dados mais fácil de entender e seguir.
O nome da variável deve refletir tanto o tipo quanto o propósito que ela desempenha no sistema, especialmente quando for necessário usá-la em uma interface (API). Além disso, ao aplicar conceitos de DDD (Domain-Driven Design), é crucial adotar uma linguagem ubíqua, ou seja, usar no código a mesma terminologia que o especialista do negócio utiliza. Um bom nome elimina a necessidade de procurar informações adicionais sobre a variável, seu preenchimento e como ela pode ser utilizada.
Como nomes inadequados afetam a manutenção e evolução do código
No desenvolvimento de software, a produtividade ao trabalhar em um sistema tende a diminuir com o tempo devido a vários fatores. À medida que o sistema cresce, a complexidade também aumenta com a adição de novas funcionalidades, o que pode resultar na erosão do código. Isso ocorre por conta de dívidas técnicas, design inadequado ou pela perpetuação de uma modelagem deficiente. Alterar o nome de uma variável, por exemplo, pode se tornar uma tarefa difícil, especialmente quando ela é exposta em uma API consumida por outros sistemas. Um nome inadequado pode causar dificuldades não apenas para quem mantém o sistema, mas também para aqueles que precisam usá-lo e entender como preenchê-lo corretamente.
A Lei de Wirth afirma que “o software está se tornando mais lento mais rapidamente do que o hardware se torna mais rápido”. Ela sugere que, apesar dos avanços no hardware, o software tende a se tornar mais complexo e menos eficiente ao longo do tempo, o que contribui para a queda da produtividade. As Leis de Lehman, baseadas em estudos sobre a evolução e manutenção de sistemas de software, descrevem como a complexidade técnica, a complexidade de negócios e a complexidade de legado interagem. Este é um tema que posso explorar com mais profundidade em outros artigos.
Manutenção contínua e refatoração: Estratégias para evitar a degradação do código
Uma maneira eficaz de combater essa degradação ao longo do tempo é adotar práticas de manutenção contínua e refatoração de código. Refatorar o código regularmente ajuda a reduzir a complexidade, melhora a legibilidade e facilita a introdução de novas funcionalidades, sem que a base de código se torne insustentável. Um bom design e uma nomenclatura clara e significativa são essenciais nesse processo, pois tornam o código mais fácil de entender e modificar, mesmo após um longo período.
Além disso, a refatoração preventiva, corrigir problemas de design e estrutura do código antes que se tornem gargalos, pode evitar a acumulação de dívidas técnicas. Isso é fundamental para manter o software ágil e adaptável às mudanças nas necessidades do negócio. A falha em refatorar o código ou a negligência com a manutenção contínua pode levar a sistemas que, embora tecnicamente viáveis, se tornam difíceis de manter, atualizar e integrar com novos sistemas.
Um exemplo simples: imagine um cenário em que você altera a regra em um método, e o nome desse método já não representa mais o que ele faz atualmente. Refatorar o nome do método é uma ação simples, mas essencial, para tornar o código consistente, legível e fácil de entender. Caso contrário, no futuro, alguém pode tentar reutilizar esse método, achando que ele ainda realiza a função esperada. Só mais tarde, o desenvolvedor perceberá que o resultado não está consistente, porque o método que ele está utilizando não faz mais o que ele imaginava.
Outro fator importante é garantir que, ao realizar mudanças, o princípio da responsabilidade única (SRP) do S.O.L.I.D. não seja comprometido. Adicionar mais regras pode violar esse princípio, gerando a necessidade de indireção, um dos princípios do G.R.A.S.P. Isso pode ser alcançado utilizando padrões como o Specification Pattern, descrito por Martin Fowler em Patterns of Enterprise Application Architecture, ou aplicando outros padrões do GoF.
Práticas como remover as variáveis que são não mais usadas na assinatura do método, Em resumo, estar atento às necessidades de refatoração durante a evolução e correção do sistema é uma prática essencial para um bom engenheiro de software.
Evite erros comuns na nomeação de variáveis
Erros de nomeação podem ocorrer em diversos cenários, geralmente por falta de conhecimento, imaturidade ou um design inadequado, frequentemente em razão da pressão do tempo. Prefiro acreditar que profissionais experientes não cometem esses erros por preguiça ou descaso com o projeto. Todos, como profissionais, fazem o seu melhor com o conhecimento e as condições de que dispõem.
“Faça o teu melhor na condição que você tem, enquanto você não tem condições melhores para fazer melhor ainda!” – Mario Sergio Cortella
Como já deu para perceber, uma ação simples, aparentemente inofensiva, como dar um nome qualquer a uma variável, pode impactar diretamente a legibilidade e manutenibilidade do código. Isso, por sua vez, gera um impacto negativo na performance de quem está fazendo a manutenção e a evolução do sistema, tornando mais difícil entender, modificar ou corrigir o código no futuro.
Além disso, um design inadequado pode levar à criação de variáveis desnecessárias, o que não só dificulta a compreensão do sistema, mas também compromete a performance do software. O consumo excessivo de memória, o aumento da complexidade e a sobrecarga de dados desnecessários são consequências diretas de um design ineficiente. Isso pode resultar em um sistema mais lento, difícil de escalar e mais desafiador de manter.
Conclusão
A escolha dos nomes das variáveis vai muito além da legibilidade: ela impacta diretamente no design do código, na manutenibilidade e até na performance da equipe que dará manutenção e evoluirá o sistema. Nomes claros, precisos e significativos ajudam a moldar a estrutura do código, tornando-o mais coeso e intuitivo, o que facilita a compreensão, modificação e evolução do sistema ao longo do tempo.
Embora a nomeação adequada seja crucial, ela não é uma solução isolada. A manutenção contínua, a refatoração regular e a aplicação de boas práticas de design são essenciais para evitar a degradação do sistema e garantir sua evolução de forma sustentável. Uma base de código com boas práticas de nomeação e design facilita o trabalho da equipe, mantendo o sistema ágil, escalável e adaptável às mudanças.
Portanto, a escolha cuidadosa dos nomes das variáveis é uma prática fundamental para garantir a qualidade do código, melhorar a produtividade da equipe e manter o software saudável e eficiente no longo prazo.
No próximo artigo, vamos explorar alguns cenários que devem ser evitados ao nomear variáveis, para garantir que o código continue limpo, conciso e de fácil manutenção.