Sabe quando encontramos aquela classe grande ou que pode se tornar ainda maior?
Na hora já lembramos da importância do SRP (Single Responsibility Principle) para facilitar a manutenabilidade, testabilidade, reduzir o acoplamento e aumentar a coesão.
Mas vamos ser sinceros: na maioria das vezes é complicado aplicar o SRP na prática!
Neste artigo vou mostrar uma técnica chamada de Esboços de Recursos que ajuda a encontrar agrupamentos que fazem sentido dentro de uma classe. Bora conferir?
📝 Esboços de Recursos#
É uma técnica descrita por Michael Feathers no livro Trabalho Eficaz com Código Legado.
A ideia é bem simples e precisamos apenas esboçar da seguinte forma:
- Listar as variáveis da classe;
- Listar os métodos (públicos e privados);
- Para cada método, desenhar uma seta indicando que o método utiliza aquele campo;
- Após os passos acima, procurar por um agrupamento de campos e métodos no qual é possível dar um nome adequado ao grupo.
Com papel e caneta é possível realizar esta técnica tranquilamente. Particularmente recomendo papel e caneta, pois é só um esboço que vai ser descartado depois.
Vamos utilizar a seguinte classe como exemplo:
public class Booking {
private int duration;
private int hourlyRate;
private Client client;
private List<ServiceCharge> charges;
public void extend(int additionalHours) {
if (additionalHours > 0) {
this.duration += additionalHours;
}
}
public void extendForDay() {
this.extend(24);
}
int getAdditionalCharges() {
int total = 0;
for (ServiceCharge charge : charges) {
total += charge.getAmount();
}
return total;
}
int getBaseCharge() {
int baseMultiplier = 1;
if (duration > 8) {
int regularHours = 8;
int overtimeHours = duration - 8;
return (hourlyRate * baseMultiplier * regularHours) +
((int)(hourlyRate * baseMultiplier * 1.5) * overtimeHours);
}
return hourlyRate * baseMultiplier * duration;
}
public int getTotalCharge() {
return getBaseCharge() + getAdditionalCharges();
}
public double getHourlyRateWithCharges() {
if (duration == 0) return 0;
return (double) getTotalCharge() / duration;
}
public boolean isLongBooking() {
return duration > 40;
}
public String summary() {
return String.format("Booking for %s: %d hours at $%d/hour. Total: $%d",
client.getName(), duration, hourlyRate, getTotalCharge());
}
public void applyDiscount(int discountPercentage) {
if (discountPercentage > 0 && discountPercentage <= 100) {
hourlyRate = hourlyRate - (hourlyRate * discountPercentage / 100);
}
}
}
✍️ Aplicando a técnica#
Primeiro, vamos listar as variáveis da classe Booking com círculos como mostrado na imagem abaixo:

Em seguida, fazemos a mesma coisa com métodos públicos e privados. E para cada um deles, desenhamos uma seta indo para as variáveis e métodos que são acessados ou modificados dentro do método:

Veja que o método Booking#extend() modifica a variável duration. Então traçamos uma reta do método até a variável. Podem ignorar construtores nesta etapa.
Após realizar os passos acima, temos o seguinte diagrama:

Após a reorganização do diagrama, podemos notar duas coisas interessantes:
- A variável
chargessó é utilizada pelo métodoBooking#getAdditionalCharges(); - Existe um conjunto de métodos com a palavra
chargeno nome.
Ou seja, talvez seja possível criar um agrupamento da seguinte forma:

Será que é viável? Para saber se é viável, tente dar um nome ou conceito que faça sentido para o agrupamento.
Se conseguir dar um nome, provavelmente é um bom candidato para separarmos em uma nova classe.
Podemos, por exemplo, criar uma classe com o nome BookingCharges que é responsável por saber calcular todos os custos envolvidos na reserva.
E percebam que saem algumas setas do agrupamento. Estas setas indicam a dependência que este agrupamento tem para funcionar de maneira correta!
No nosso exemplo, as variáveis hourlyRate e duration são variáveis que devem ser passadas para o BookingCharges.
E por que eu não incluí estas duas variáveis no agrupamento? Eu poderia! Mas se eu fizer isso, eu teria que colocar no agrupamento, por exemplo, os métodos que dependem destas variáveis também!
E a vantagem desta técnica está aí.
Eu consigo desenhar, apagar e verificar se meu agrupamento faz sentido ou não de forma visual e rápida.
🔍 Analisando o agrupamento#
Vamos olhar para o código refatorado considerando o agrupamento descoberto:
public class BookingRefactored {
private int duration;
private int hourlyRate;
private Client client;
/* NEW */
private BookingCharges charges = new BookingCharges();
public void extend(int additionalHours) {
if (additionalHours > 0) {
this.duration += additionalHours;
}
}
public void extendForDay() {
this.extend(24);
}
/* NEW */
int getAdditionalCharges() {
return charges.getAdditionalCharges();
}
/* NEW */
int getBaseCharge() {
return charges.getBaseCharge(duration, hourlyRate);
}
/* NEW */
public int getTotalCharge() {
return getBaseCharge() + getAdditionalCharges();
}
/* NEW */
public double getHourlyRateWithCharges() {
return charges.getHourlyRateWithCharges(duration, hourlyRate);
}
public boolean isLongBooking() {
return duration > 40;
}
public String summary() {
return String.format("Booking for %s: %d hours at $%d/hour. " +
"Total: $%d",
client.getName(), duration, hourlyRate, getTotalCharge());
}
public void applyDiscount(int discountPercentage) {
if (discountPercentage > 0 && discountPercentage <= 100) {
hourlyRate =
hourlyRate - (hourlyRate * discountPercentage / 100);
}
}
}
E a nova classe:
public class BookingCharges {
private List<ServiceCharge> charges;
int getAdditionalCharges() {
int total = 0;
for (ServiceCharge charge : charges) {
total += charge.getAmount();
}
return total;
}
int getBaseCharge(int duration, int hourlyRate) {
int baseMultiplier = 1;
if (duration > 8) {
int regularHours = 8;
int overtimeHours = duration - 8;
return (hourlyRate * baseMultiplier * regularHours) +
((int)(hourlyRate * baseMultiplier * 1.5) * overtimeHours);
}
return hourlyRate * baseMultiplier * duration;
}
public int getTotalCharge(int duration, int hourlyRate) {
return getBaseCharge(duration, hourlyRate) + getAdditionalCharges();
}
public double getHourlyRateWithCharges(int duration, int hourlyRate) {
if (duration == 0) return 0;
return (double) getTotalCharge(duration, hourlyRate) / duration;
}
}
As vantagens do novo código são claras:
🎯 Responsabilidade mais clara#
Bookingé responsável pelo estado e operações gerais;BookingChargeé responsável especificamente pelo cálculo dos custos.
🧪 Testabilidade#
Não precisamos de uma configuração completa do Booking para testar apenas o cálculo de custos.
🛠️ Manutenabilidade#
- Dependência clara entre classes;
- Mudança na lógica do cálculo de custos está isolada.
🏗️ OCP (Open-Closed Principle)#
- Possibilidade de alterar como é calculado o custo, sem alterar o
Booking; - Possibilidade de ter múltiplas estratégias de cálculo de custo.
E entre outros benefícios!
🤔 Quando devo refatorar uma classe grande?#
Não saia refatorando uma classe grande sem motivo nenhum, hein!
Exemplos de quando não refatorar:
- O sistema é simples e não vai crescer;
- Os cálculos de cobrança são triviais e improváveis de mudar.
Exemplos de quando refatorar:
- O sistema deve evoluir;
- Os cálculos de cobrança podem se tornar complexos;
- Você precisa reutilizar a lógica de cobrança em outro lugar;
- A equipe prefere separação de responsabilidades e testabilidade.
Ou seja, aplique sempre a regra do escoteiro!
Precisa modificar a classe e ela está grande ou vai se tornar maior? Então refatore e experimente esta técnica!
🚫 Quando esta técnica não é viável#
Esta técnica não é viável quando a classe é muito grande.
Por exemplo, já vi classes com mais de 200 variáveis e 500 métodos. Nesses casos, não vale a pena o esforço manual. Existem outras técnicas de refatoração mais adequadas para esta situação extrema.
📚 Referências#
Para saber mais, recomendo a leitura dos guias oficiais:
✨ Conclusão#
A técnica de Esboços de Recursos é uma ferramenta visual poderosa e barata para identificar responsabilidades ocultas em classes que cresceram demais. Ela nos ajuda a “ver” o código de uma outra perspectiva antes de sair escrevendo linhas e mais linhas.
Experimente usar papel e caneta na próxima vez que se deparar com aquele código “God Class”. O resultado pode te surpreender!
E você, já conhecia essa técnica? Como você lida com classes gigantes no seu dia a dia?
Deixe seu comentário abaixo!
🥒🥒 Até o próximo artigo! 🥒🥒


