Concrete Logo
Hamburger button

Código Limpo e a Importância de Nomear

  • Blog
  • 23 de Abril de 2018

Para desenvolver qualquer produto, é necessário estar constantemente nomeando. Concorda? Temos que criar nomes para variáveis, funções, argumentos, classes… Neste post, vamos falar sobre a importância de criar bons nomes e como eles influenciam positivamente no seu código. Vamos lá?

Utilizando Nomes Significativos

Primeiramente, devemos ter em mente o quão grande é a importância de nomear no desenvolvimento de software.
Bons nomes podem economizar muito tempo na leitura do seu código, e é por isso que você pode gastar sem problemas um tempinho a mais para garantir que está nomeando da melhor maneira possível. Vamos ver um exemplo?

O código acima não é complexo, é pequeno, mas ainda assim temos dificuldade em dizer o que ele está fazendo. O que é *lista* e o que estamos armazenando nela? Qual é a importância do primeiro elemento, zero, de um item da *lista*? O que significa o valor *4*? Como posso usar a *List<int[]>* que está sendo retornada?

É muito difícil responder essas perguntas sem conhecimento prévio do ambiente em que o código está inserido, e isso é um problema, pois em times ágeis e multidisciplinares qualquer desenvolvedor deve entender claramente o seu código, não importa se é novo ou antigo em um projeto. Concorda? Vamos ver esse mesmo código melhorado:

Este código não está limpo, mas elimina quase tudo o que antes estava implícito. Com isso, é fácil enxergar que o problema não está na complexidade de um código, e sim na quantidade de coisas implícitas. Quanto mais coisas implícitas seu código possui, mais difícil é a sua leitura; seja para qualquer outro desenvolvedor, ou para você mesmo – o próprio autor – um tempo depois.

Podemos falar sobre código limpo com mais detalhes ao longo de outros posts, mas para começarmos a desenvolver um olhar crítico sobre o nosso próprio código, segue um exemplo de código limpo:

 

Evite Confusões

Como desenvolvedores, não queremos dar falsas pistas sobre o entendimento do nosso código, certo? Alguns nomes de variáveis podem mascarar ou até indicar uma ideia diferente da rotina escrita para outro desenvolvedor que está lendo. Por exemplo:

 

As variáveis *rm*, *cd* e *ls* têm nomes de comandos Unix amplamente usados. Entretanto, para o código desenvolvido, são nomes curtos e sem significado algum. A tendência é que, por isso, o desenvolvedor que está lendo associe automaticamente o propósito dessas variáveis no código com a funcionalidade desses comandos conhecidos.

O caso da variável *nulo* é simplesmente visual. As palavras *nulo* e *null* são muito parecidas, diferenciadas apenas pela sua última letra. Constantemente, quando estamos lendo, não lemos uma palavra inteira e a proximidade entre o nome da variável e a palavra protegida *null* pode causar desentendimento

Para enfatizar que não lemos uma palavra letra por letra, vamos observar o exemplo abaixo:

Os nomes das duas classes acima são muito parecidos e difíceis de diferenciar até em uma leitura simples como no exemplo. Imagine o quanto piora em um código real?

Existem casos que levam os desenvolvedores a entender de outra forma o funcionamento de certas variáveis por causa da nomenclatura utilizada. No exemplo abaixo, a variável *nomesList* pode sugerir um comportamento diferente da sua declaração.

Ao ler a palavra *List* em *nomesList*, imediatamente associamos essa variável a alguma implementação da interface List. O ideal é não utilizar tipos ao nomear uma variável, a não ser que seja bem necessário e útil, como é o caso das instâncias de *View* no desenvolvimento mobile. Nesse caso, utilizar simplesmente *nomes* seria bom; melhor ainda seria algo mais explícito como *nomesDosAlunos*.

Diferenças Significativas

Vamos começar com um exemplo simples, uma função que copia dois vetores de char:

Nossa primeira dúvida ao olhar essa função é saber quem é o vetor base e quem é o destino da função. Em seguida, tentamos fazer atribuições sem certeza, pensando que o padrão foi seguido e o primeiro argumento é a base e o segundo é o destino. O último passo é assumir a insegurança e parar o fluxo do nosso trabalho só para testar o comportamento dessa função.

Com a função escrita desta maneira, com certeza não perderíamos tempo questionando o comportamento da função e, muito menos, pararíamos nosso fluxo de trabalho para realizar testes que não agregam valor ao nosso sistema.

É comum que determinadas funções tenham comportamento semelhante no nosso código. Sempre que isso acontecer, devemos ter o trabalho de indicar aos outros desenvolvedores a utilização certa e a diferença entre essas rotinas.

O fato dos escopos das funções estarem reduzidos e os retornos omitidos não possui uma relevância tão significativa. O que realmente importa é que embora tenhamos nomes diferentes nas quatro funções, não temos de fato uma diferença de sentido entre elas. É como nomear duas classes, uma chamada *ContaInfo* e outra *ContaDado*; ambas possuem nomes diferentes, mas qual é a diferença prática?

Caso você não tenha se convencido ainda por causa da falta de retorno das funções, deve lembrar que no meio do código não teremos sempre o escopo disponível e teríamos que consultar sua declaração toda hora. Mesmo que as IDEs ajudem nessa consulta, é improdutivo ter que fazer isso várias vezes ao longo do desenvolvimento, principalmente se a definição real está em outra camada do sistema.

Não Use Abreviações

Abreviar nomes contribui para o aumento de coisas implícitas no código, ou seja, aquelas que necessitam de conhecimento prévio do leitor para que haja um perfeito entendimento. Exemplo:

 

O que podemos dizer sobre as variáveis *cDtMdhms* e *mDtMdhms*? Como podemos pronunciar os seus nomes para qualquer tipo de discussão sobre esse código?

Agora ficou muito mais fácil de entender, mesmo sem conhecimento prévio do sistema, que existe uma classe *DocumentoInscricao* relativa a algum documento de inscrição e que esse documento possui um carimbo de data de criação e da sua última modificação. Isso tudo foi possível apenas nomeando de maneira correta e significativa, tornando explícito qualquer sentido antes escondido.

Prefixos e Sufixos

Antigamente, os compiladores não checavam os tipos das variáveis e os programadores precisavam lembrar constantemente o tipo das suas variáveis declaradas. Para ajudar, era muito comum nomear variáveis e utilizar o seu tipo como sufixo. Hoje em dia, existem diversas IDEs que podem nos ajudar com esse tipo de problema, então não vale a pena utilizar esse tipo de nomenclatura e correr o risco de prejudicar o nosso código, concorda? Vamos ver um exemplo de nome inconsistente:

Certamente a variável correspondente a um número de telefone foi escrita inicialmente com o tipo String, porém – em algum momento – a mudança foi necessária e o nome permaneceu o mesmo, o que pode causar confusão no código.

Outro ponto importante é que não precisamos de qualquer prefixo para diferenciar atributos da classe de outras variáveis. Prefixos como *m* só poluem o código e rapidamente aprendemos a ignorá-los, pois não trazem nenhum benefício, principalmente com o avanço das IDEs e suas diferentes cores que nos ajudam no desenvolvimento. Olha só um exemplo de código bagunçado:

 

Conforme aprendemos a ignorar o prefixo *m*, a leitura do código é prejudicada, já que a tendência é lermos *ScrollListener* tanto para a classe quanto para a variável. Vale a pena ressaltar que nossas classes devem ser pequenas, então não vamos perder o controle de suas variáveis e nem dos seus tipos.

Existe um prefixo famoso para interfaces que nem sempre passa informação com clareza para os leitores do código. Por exemplo, o que podemos dizer sobre o nome *IAnimal*?

Até onde sabemos, esse prefixo com a letra *i* maiúscula pode significar *Interface* ou *Implementation*. É legal esse tipo de codificação, mas caso julgue necessário diferenciar a interface da sua classe de implementação, utilize algum prefixo ou sufixo que não gere dúvidas e que seja escrito na classe de implementação, como por exemplo
*AnimalImpl*. É melhor deixar suas interfaces livres de codificação nos seus nomes, evitar distrações ou informações desnecessárias e não influenciar outros desenvolvedores a criar um padrão de prefixo ou sufixo.

Outro problema com prefixos são os prefixos de projeto. Imagine um sistema chamado “Sistema de Gambiarras Predominantes”, dado pela sigla SGP. Se começarmos a criar classes como *SGPMensagem*, *SGPCliente*, *SGPRota*… com o mesmo padrão de prefixo, nosso sistema fica poluído e surgem vários efeitos negativos. Entre eles podemos citar a perda de produtividade ao usar uma das melhores funcionalidades de uma IDE, o *autocomplete*, simplesmente por adicionar às classes um prefixo que não agrega nenhum valor.

É bastante antiprodutivo termos que digitar sempre a sigla SGP para, então, localizar a classe que queremos utilizar. Vale também ressaltar o aumento da dificuldade de identificar a classe que desejamos utilizar em uma lista em que a ordem alfabética é significativa somente a partir da quarta letra.

Exceção

Entretanto, como toda regra, essa também tem sua exceção. Existem casos nos quais sufixos são recomendados, já que facilitarão a vida do desenvolvedor que vai ler o nosso código. Quando utilizamos algum padrão de projeto, é muito mais fácil para o entendimento – e até construção – do código quando nomeamos interfaces ou classes com sufixos que representam um padrão. Vamos ver um exemplo de nomenclatura para design patterns:

Apenas lendo os nomes acima, conseguimos entender sem conhecimento prévio do sistema que existem três interfaces: uma que funciona como ouvinte de cliques, uma que funciona como uma fábrica de formas geométricas e um construtor por etapas específicas de um alerta.

Seja Consistente

É importante escolher um padrão para o nosso código e permanecer com ele ao longo de todo o projeto. É prejudicial para o entendimento do nosso código utilizar diferentes nomenclaturas para ações semelhantes. Por exemplo:

 

Qual é a diferença entre os três métodos acima? O método *getNome()* pode fazer parte dos *getters e setters* da classe, mas e o *recebeNome()* e *obtemNome()*? Qual é a diferença entre esses dois? Pior, pode ser que eles não tenham diferença alguma na sua funcionalidade. Seria melhor estabelecermos um padrão e utilizarmos consistentemente no nosso código.

Caso exista diferença entre os três métodos, podemos separá-los por classes e sempre utilizar a nomenclatura *get*.

Nosso padrão também deve ser aplicado às classes que criamos.

*Manager* e *Controller* são duas palavras genéricas no meio de desenvolvimento de sistemas. Nem sempre é trivial explicar o que um dos dois faz, e ter esses dois nomes juntos pode complicar ainda mais, fazendo surgir dúvidas como “O que faz um Controller e um Manager?” ou “Por que as duas classes não são um Controller ou um Manager?”.

Ficou claro? Tem alguma dúvida ou alguma contribuição? Aproveite os campos abaixo! Até a próxima =)

A Concrete, parte da Accenture Technology, é referência em consultoria ágil, facilitando a transformação digital dos clientes ao criar produtos digitais inovadores. Só no capítulo Android contamos com 60 pessoas, que compartilham informações o tempo todo, em talks semanais e treinamentos frequentes. Nossos times têm autonomia para trabalhar e acesso direto aos gerentes, o que facilita a troca de conhecimento e a constante evolução técnica. Quer trabalhar com os melhores? Acesse: concrete.com.br/vagas