Tem um tempinho para conversarmos sobre boas práticas de programação com Java?

Mariana Azevedo
9 min readFeb 27, 2018

Para ser um bom desenvolvedor de software em Java ou em qualquer outra linguagem além de estudar bastante, deve-se dominar conceitos e convenções para deixar o código limpo legível e fácil de ser mantido. Além disso, existem inúmeros motivos pelos quais se recomenda a adoção de boas práticas no nosso dia-a-dia:

  • Dificilmente um código será sempre mantido e evoluído pelo mesmo desenvolvedor. Dessa forma, quanto melhor o código, menos tempo um segundo desenvolvedor gastará para entendê-lo;
  • A produtividade da equipe aumenta com um código bem escrito, pois facilita a leitura, o entendimento e, certamente, reduz a quantidade de treinamentos que deveriam ser feitos para alinhar o conhecimento;
  • Reduz a complexidade excessiva do código de novas funcionalidades, pois a manutenção de um código mal estruturado pode levar tempo e maior complexidade no desenvolvimento de novos módulos. Além disso, evita que uma alteração ou adição de funcionalidade, mesmo que simples, faça uma funcionalidade não relacionada parar de funcionar;
  • Reduz a quantidade de bugs, pois tende ser mais fácil de revisar e testar, além de fazer o que promete, entregando valor ao cliente;
  • Por fim, convenções padronizam um conjunto de práticas estabelecidas que são bem conhecidas pelos desenvolvedores mais experientes na área ou até mesmo adotadas inconscientemente por outros devs. Essas práticas servem de guia para os mais jovens na área, a criar um corpo de conhecimento e programar de maneira correta.

Sem mais delongas, seguem algumas dicas importantes e bem simples para manter seu código nível Jedi:

1. Nomear variáveis e métodos de forma descritiva

Para termos um código limpo, devemos nomear variáveis, funções, parâmetros, classes ou métodos de acordo com suas funcionalidades. Isso é essencial para um bom entendimento do código. Ou seja, nada de variável com nome i para salvar a idade de uma pessoa ou de aux para nomear uma variável com papel de auxiliar.

Geralmente o tamanho usado é de 1 (um) caractere para contadores em loops; 1 (uma) palavra para variáveis de loops e condicionais; 1–2 palavras para nomes de métodos; 2–3 palavras para nomes de classes; 3–4 palavras para nomes de variáveis globais. Não use tamanhos muito longos para nomes de variáveis (mais de 50 caracteres). Caso contrário ficará difícil de ler o código, além de que o mesmo pode não rodar em alguns compiladores por causa da limitação de tamanho das linhas.

Além disso, não use “_” (underscore) em lugar algum exceto para constantes e valores de enums. Não reuse o mesmo nome de variável na mesma classe em diferentes contextos. E outra coisa: decida pelo uso de uma linguagem natural apenas, ou seja, só português ou só inglês.

2. Evite código “Hadouken”. Simplifique-o

O código é uma linguagem e escrevê-lo é tão igual a escrever um texto. E quanto mais simples esse texto for, mais claro será o significado do que está sendo feito. A mesma regra se aplica para programação: para termos um código limpo, é necessário criar funções simples, pequenas, claras e com baixa complexidade, evitando que o seu código parece ter sofrido um “Hadouken” (se você já jogou Street Fighter, já pegou o que eu estou falando, né? Para quem não sabe, é um código com muitos condicionais aninhados).

Exemplo de código com condicionais aninhados. Favor, não seguir o exemplo.

O nosso grande guru do Clean Code, Robert Martin (nosso Uncle Bob), nos ensina o seguinte: as funções devem ter apenas uma responsabilidade e que a cumpra da maneira mais simples possível. Assim um método pode ser reutilizado quantas vezes for ser necessário em seu código, facilitando sua manutenção à longo prazo.

3. Comentários são bons, mas somente o necessário

Existe uma máxima sobre os comentários: se o código precisa de muitos comentários, significa que não é um bom código. Então, comente apenas o necessário. Códigos são constantemente modificados, ao contrário dos comentários.

Se um comentário não evolui junto com o código, ele pode deixar de ter um significado pertinente e pode guiar a quem está trabalhando no código à um entendimento equivocado. Portanto, caso seja necessário comentar, nunca esqueça de revisá-los e deixá-los alinhados com o que o código representa.

E por fim, usem Javadoc na criação de classes e métodos. O Javadoc é essencial para que outras pessoas entendam seu código e consigam utilizá-lo da maneira correta.

4. Don’t repeat yourself

Para quem tem dificuldades com o inglês, essa expressão pode ser traduzida como “Não repita a si mesmo”. Em outras palavras é “Não copie e cole código indiscriminadamente”. Evitar repetição é uma boa prática porque, conceitualmente, as partes de um sistema devem possuir uma única responsabilidade. Esse conceito faz parte dos princípios SOLID, introduzidos pelo Uncle Bob, e tem um nome específico para ele: Single Responsibility Principle (Princípio da responsabilidade única).

O problema da repetição é que a medida que um sistema cresce, manter um código desse tipo gera uma complexidade muito alta. O correto é utilizar o conceito de abstração no trecho de código duplicado (extrair um método ou classe/interface, centralizando a implementação) e reutilizá-lo onde for necessário.

5. Programação defensiva

Um bom desenvolvedor deve ser pessimista, sempre pensar no pior cenário e se preparar para lidar com os problemas que eventualmente podem acontecer. Em Java, os recursos mais comuns para lidar com erros são as Exceptions e a utilização do try-catch.

As exceptions são recursos que indicam que naquele método pode ocorrer um fluxo diferente do previsto. Por exemplo, tentar atualizar um objeto em um trecho de código em que ele é nulo ou tentar inserir uma variável do tipo texto em uma numérica. O try-catch serve para capturar as exceções.

Uma boa prática em uma implementação com try-catch, é utilizar o finally, principalmente se o seu trecho de código utiliza algum recurso de conexão com o banco de dados ou utiliza buffer para leitura e escrita em arquivos, pois é muito útil para liberar recursos do sistema. A função do finally é sempre executar o trecho de código dentro dele, mesmo que uma exceção seja lançada.

Outra boa prática para evitar erros no seu código é não utilizar “null”, tanto como parâmetro, quanto para retorno em funções. Na maioria das vezes, essas implementações exigem verificações desnecessárias que, uma vez esquecidas, provocam erros no processo.

Além disso, ao lançar uma exceção, seja específico. Evitem exceções genéricas e não lance diretamente Exception ou RuntimeException. Exceções genéricas pecam pela ausência de detalhamento do tipo de erro que ocorreu. Se nenhuma existir, crie a sua própria classe de exceção que seja especifica o bastante.

6. Design Patterns (padrões de projeto)

Se você já está no mercado há algum tempo, já deve ter ouvido algum desenvolvedor mais experiente falar sobre ou se ainda é um estudante e fez alguma disciplina mais avançada de Engenharia de Software, já ouviu algum professor falar sobre Design Patterns ou, em português, padrões de projeto. Parece ser uma coisa de outro mundo, incrível e revolucionária, mas, na verdade, é um conjunto de soluções para problemas do nosso dia a dia.

De forma simplificada, esses padrões são divididos em três categorias: os de criação, os estruturais e os comportamentais. Como o artigo não é exclusivo de padrões de projeto, vou elencar três situações comuns, que podem ser resolvidas de forma bem simples utilizando um padrão de projeto:

  • Quando eu tenho um objeto muito complexo para ser criado: veja a classe Venda que utilizamos nos exemplos do item 6.
Exemplo de um objeto complexo

Uma ótima maneira de resolver esse problema, é aplicar o padrão Builder. Sempre que tivermos um objeto que possui muitos atributos ou uma lógica de criação mais complexa, podemos encapsular isso em um Builder. Dessa forma, o código de criação fica centralizado, facilitando a manutenção e evolução do mesmo. Veja como a implementação fica mais simples e limpa:

Exemplo de aplicação do padrão Builder
  • Quando tenho comportamentos em uma classe que utilizam comportamentos de outra: por exemplo, temos a abstração Desconto, que representa um desconto cobrado na venda, que pode ser decorado por outros tipos de descontos.
Exemplo de aplicação do padrão Decorator

Em seguida, implementamos descontos concretos (MDR — Merchant Discount Rate, Tarifa Administrativa, Taxa de Aluguel de Máquina de Cartão, etc…) que, após realizarem o seu serviço, invocam o próximo desconto a ser calculado para termos o valor real da Venda. Sempre que percebemos que temos comportamentos que podem ser compostos por comportamentos de outras classes envolvidas em uma mesma hierarquia, o ideal é utilizar o padrão Decorator.

  • Quando eu tenho várias estruturas semelhantes repetidas em várias partes do código ou mesmo do sistema: nesse caso, o padrão Template Method é uma boa forma de solucionar esse problema. Com ele, conseguimos definir uma estrutura mais genérica e deixar “buracos”, que serão implementados de maneira diferente conforme uma implementação específica necessite. Veja o exemplo abaixo da classe abstrata Relatorio. Ela é um template simples que contém todas as partes de um relatório: cabeçalho, conteúdo e o rodapé. A partir desse template, podemos criar classes de relatórios específicos, como no exemplo abaixo, da classe RelatorioDeVendasTrimestral, com uma implementação específica.
Exemplo de aplicação do padrão Template Method

Dessa forma, ao invés de repetirmos código, conseguimos reutilizá-lo e facilitamos possíveis evoluções, tanto do código em sua estrutura genérica, quanto do código mais específico.

7. Otimize o código com recursos mais recentes da linguagem

Procure sempre estar atualizado com os recursos da versão mais atual da linguagem que você trabalha. Especificamente em Java, a versão atual mais consolidada é o Java 8, que tem vários recursos e API’s que facilitam o desenvolvimento e reduz a complexidade e o tamanho do código. Temos alguns ótimos recursos que vale a pena ser comentados:

  • Lambda Expressions: são recursos muito disseminados em linguagens funcionais e facilita muito a vida do desenvolvedor. Esse recurso não é uma novidade para quem já trabalhou com as linguagens Groovy, Scala ou Clojure. A grande vantagem das expressões lambda é reduzir a quantidade de código necessária para se escrever funções. Basicamente, uma função lambda é uma função sem declaração, isto é, não é necessário colocar um nome, um tipo de retorno e o modificador de acesso. A ideia é que o método seja declarado no mesmo lugar em que será usado. Como exemplo, imagine um sistema financeiro de uma empresa, em que precisamos ordenar as vendas do dia pelo número de parcelas.
Exemplo de lambda expression

Essa sintaxe utilizada no Comparator é a sintaxe do Lambda no Java 8.

  • Stream API: traz classes e métodos novos para manipular coleções de maneira mais simples e eficiente. Dentre as features mais interessantes da API de collections está o método stream(), que possibilita encadear chamadas de métodos, dependendo das operações que são necessárias para obter o resultado que queremos. Por exemplo, imagine o mesmo sistema financeiro do exemplo acima, mas dessa vez precisamos filtrar todas as vendas à crédito do dia. Veja um exemplo de uma implementação com Java 7:
Exemplo implementação loop Java 7

Agora, observe a mesma lógica feita com stream() do Java 8:

Exemplo implementação do stream()

Consegue perceber que código fica bem mais enxuto?

  • Date and Time API: lidar com datas nas versões mais antigas do Java nem sempre era uma tarefa simples e no decorrer dos anos algumas boas bibliotecas foram criadas para contornar problemas típicos de conversão e precisão. Na versão 8, uma API mais poderosa foi criada, baseada na famosa biblioteca JodaTime. O novo pacote, denominado de java.time, possui várias classes para trabalhar com objetos que armazenam apenas datas, horas ou mesmo ambos de forma simultânea. Por exemplo, queremos representar uma data e hora no fuso horário de São Paulo.
  • Default Methods: introduz possibilidade de termos métodos concretos em interfaces, por meio do uso do modificador default. Dessa forma, esses métodos podem ser herdados por qualquer classe que implemente interfaces com essas características. Veja a implementação da interface TimezoneCliente:

Perceba que posso estender essa interface para criar outra para tratar casos em que o timezone é inválido.

Criar código limpo deve ser, sem dúvidas, a missão de qualquer desenvolvedor que realmente se importa com os caminhos que nos levam ao produto final do software. Basicamente, a chave para criar um bom código é: ser conciso e claro na nomenclatura, criar funções simples com responsabilidades bem definidas e únicas, não ser repetitivo, comentar somente quando necessário e o necessário, se atentar para os possíveis erros e otimizá-lo utilizando os melhores recursos disponíveis, seja da linguagem ou da teoria.

E a dica mais importante para quem deseja ser mais produtivo como desenvolvedor: estude! Estude sempre! Não adianta promover melhorias no comportamento, como ter foco e ser auto organizado, nem memorizar dicas como essas sem se aprofundar no tema e dedicar-se à prática. A constância em buscar conhecimento, absorvê-los e a disciplina de saber aplicá-los no contexto correto, certamente será o combustível para sua evolução profissional.

Os exemplos completos que foram utilizados no artigo podem ser encontrados no projeto artigo-boas-praticas-medium no Github.

Espero que tenham gostado do post. Abraços!

Referências
1. Código Limpo: Habilidades Práticas do Agile Software (Robert C. Martin, 2011)
2. Java Code Conventions (http://www.oracle.com/technetwork/java/codeconventions-150003.pdf)
3. Alura — Cursos Online de Tecnologia - Design Patterns Java I: Boas práticas de programação
4. Alura — Cursos Online de Tecnologia — SOLID com Java: Orientação a Objetos com Java
5. Alura — Cursos Online de Tecnologia — Java 8: Tire proveito dos novos recursos da linguagem

--

--

Mariana Azevedo

Senior Software Developer/Tech Lead, master in Computer Science/Software Engineering, Java, open source, and software quality enthusiast.