DEV Community

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

Posted on • Edited on

3 2 2 2 2

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)

Great read:

Is it Time to go Back to the Monolith?

History repeats itself. Everything old is new again and I’ve been around long enough to see ideas discarded, rediscovered and return triumphantly to overtake the fad. In recent years SQL has made a tremendous comeback from the dead. We love relational databases all over again. I think the Monolith will have its space odyssey moment again. Microservices and serverless are trends pushed by the cloud vendors, designed to sell us more cloud computing resources.

Microservices make very little sense financially for most use cases. Yes, they can ramp down. But when they scale up, they pay the costs in dividends. The increased observability costs alone line the pockets of the “big cloud” vendors.

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay