Refatorando códigos Java como um ninja 🥋
Tags: refactoring, java, development, braziliandevs
Published: 2024-04-01 11:06:29


> "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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mvinqewv5x3p29hkwwjf.png)

## 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?

{% twitter 1770307978031161629 %}

**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:

```java
// 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;
}
```
Refatorando...
```java
// Refatorando com guard clauses
public Optional<Result> getPayAmount(Result result) {
   if (isDead) return deadAmount();
   if (isSeparated) return separatedAmount();
   if (isRetired) return retiredAmount();

   return normalPayAmount();
}

```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ktq20zoitxdkhhbraj50.png)

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.

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

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.

```java
/*
Exemplo com streams
*/
List<String> names = input
   .stream()
      .filter(e -> e.getJob().equals("programmer"))
      .map(Employee::getName)
      .collect(Collectors.toList());
```
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()](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#reduce-T-java.util.function.BinaryOperator-) e o [sorted()](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#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 📖
- [Refatoração (2ª edição) - Martin Fowler](https://ler.amazon.com.br/kp/embed?aEditStatssin=B087N8LKYB&preview=newtab&linkCode=kpe&ref_=cm_sw_r_kb_dp_BBJ3AGT4V40ZP8KS078V)
- https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html
- https://www.oracle.com/br/technical-resources/articles/java/processing-streams-java-se-8.html
- https://refactoring.guru/pt-br/consolidate-conditional-expression