DEV Community

Cover image for Refatorando códigos Java como um ninja 🥋
Antonio Amaral
Antonio Amaral

Posted on • Updated on

Refatorando códigos Java como um ninja 🥋

"Qualquer tolo escreve um código que um computador possa enteder. Bons programadores escrevem códigos que os seres humanos podem entender." (M. Fowler, 1999)

Introdução 🥷🏻

Na minha curta jornada como programador, já me deparei com códigos excelentes. Trechos lindos e fluentes, que até poderiam ser classificados como over-engineering. Por outro lado, alguns códigos foram de dar dor no coração de qualquer dev. 💔

A refatoração é muitas vezes subestimada por pessoas non-tech. Porém, assim que alguém precisar entender como o código ruim funciona, será necessário tomar alguma atitude a respeito.

A importância da refatoração 🌸

Sempre me interessei pelo tema de refatoração. Re-escrever um código, deixar ele mais clean, mais fluente.

E, recentemente, finalizei o livro Refatoração, por Martin Fowler. Na minha trajetória de livros tech, esse foi o mais especial. O autor passa pelos princípios da refatoração, sendo o core do livro os exemplos de cada anti-pattern passível de mudanças.

Com os exemplos, Fowler explica como refatorar cada caso.

Além disso, é notável o foco na importância dos testes. Os testes estão muito atrelados a refatoração. Não existe refatoração sem teste.

"Penso nos testes como um detector de bugs para me proteger contra meus próprios erros." (M. Fowler, 1999)

  • O ciclo mencionado, A.K.A test driven development: The TDD flux. Write code -> test -> refactor

Stop using else 🍥

Passado o clickbait, o tweet abaixo é uma piada feita pelo grande Felippe Regazio. Esse tema repercurtiu muito na #bolhadev em 2024. Afinal, pode ou não usar else?

Claro que pode! Porém, o uso de else é muitas vezes visto com maus olhos, pois geralmente existe uma lógica mais simples para substituí-lo. Veremos mais no tópico abaixo.

Show me the code! 😝🍜

Substituindo condições aninhadas por Guard Clauses 🎎

Condições aninhadas muitas vezes dificultam a vida de quem está lendo. Descobrir o fluxo do código a cada execução se torna um desafio. Nesse caso é recomendável a refatoração com Cláusulas de Guarda.

Usaremos os seguintes passos:

  1. Selecionar a condição externa a ser substituída e alterá-la para uma cláusula de guarda.
  2. Testar.
  3. Se os resultados das cláusulas forem diferentes, seguimos a vida. Se forem iguais, partir para a estratégia de 'Consolidar expressões condicionais'.

Acompanhe o exemplo abaixo:

// Exemplo original - nested conditionals
public Optional<Result> getPayAmount (Result result) {
   if (isDead) result = deadAmount();
   else {
      if (isSeparated)
         result = separatedAmount();
      else {
         if (isRetired)
            result = retiredAmount();
         else {
            result = normalPayAmount();
      }
   }
   return result;
}
Enter fullscreen mode Exit fullscreen mode

Refatorando...

// Refatorando com guard clauses
public Optional<Result> getPayAmount(Result result) {
   if (isDead) return deadAmount();
   if (isSeparated) return separatedAmount();
   if (isRetired) return retiredAmount();

   return normalPayAmount();
}

Enter fullscreen mode Exit fullscreen mode

Os dois modelos de condicionais tem propósitos diferentes. Se ambas as condições fazem parte do comportamento normal, o mais recomendado realmente é o if e else. Porém, se a condição for incomum, faço a verificação e retorno apenas se for verdadeira, ou seja, cláusulas de guarda.

Extra: a classe Collections 🏯 e a Streams API 🎇

A partir da versão 8 do Java SE, podemos combinar a classe Collections juntamente a API de Streams, criando pipelines de coleção.
The pipeline operations from Streams API

Utilizaremos essas pipes para processar uma série de operações, cada uma consumindo e gerando uma coleção. Ao invés de utilizarmos o for junto ao if e else - o como fazer algo - vamos apenas expressar código de forma declarativa.

Veja o exemplo abaixo, que percorre um array de names. Caso o valor de i.job seja 'programmer', ele adiciona o i.name no array.

/*
Exemplo com for e if
*/
List<String> names = new ArrayList<>();
for (Employee e : input) {
   if (e.getJob().equals("programmer")) {
      names.add(i.getName());
   }
}
Enter fullscreen mode Exit fullscreen mode

Agora utilizando streams: chamamos o .filter() com nosso predicado, que vai funcionar como o o 'if'. Em seguida, temos nossa ação com o .map(), que retorna uma stream com o resultado da função aos elementos dela mesma.

/*
Exemplo com streams
*/
List<String> names = input
   .stream()
      .filter(e -> e.getJob().equals("programmer"))
      .map(Employee::getName)
      .collect(Collectors.toList());
Enter fullscreen mode Exit fullscreen mode

Fica evidenciado que a lógica é muito mais fácil de se entender quando escrita através de uma pipeline. 😉

Além disso, existem outros métodos úteis, como o reduce() e o sorted().

Considerações finais ⛩️

  • Existem diversos tipos e propósitos de refatoração. Nesse artigo abordei as técnicas que mais vejo carência em códigos legados e pull requests.
  • Para mais exemplos, sugiro a leitura do Refatoring por Martin Fowler. Abraços!

Referências 📖

Top comments (0)