"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:
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:
- Selecionar a condição externa a ser substituída e alterá-la para uma cláusula de guarda.
- Testar.
- 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;
}
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();
}
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.
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());
}
}
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());
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!
Top comments (0)