Segundo o próprio Robert C. Martin (Uncle Bob), o SRP (Single Responsibility Principle) é com certeza, de todos os 5 princípios SOLID, o menos compreendido. Sinto em ter que dizer isto, mas ele está certo.
Vamos só esclarecer uma coisa:
- O que vamos tratar aqui não é a minha opnião e sim um conteúdo baseado na definição que o próprio criador deste conjunto de princípios desejava passar como mensagem quando publicou o seu artigo.
Definição do SRP
Sem mais delongas, vou agora te apresentar a frase que melhor define o Princípio da Responsabilidade Única:
"Um módulo deve ser responsável por um, e apenas um, ator."
Sim, é isso mesmo! Ele define um módulo como um arquivo fonte e, dependendo da linguagem de programação, um módulo pode ser definido como um conjunto coeso de funções e estrutura de dados. Em C#, por exemplo, podemos entender esta definição de módulo como uma classe.
Violação do SRP
A duplicidade de código é algo que todo bom desenvolvedor tenta evitar e por isso criamos métodos com trechos de código que podem ser reaproveitados.
Quando escrevemos um método com trechos de código contendo parte de uma regra de negócio que pode atender mais de um ator, devemos ter um pouco mais de cautela e cuidado para entender se isso não pode nos gerar um problema.
Vamos imaginar um cenário (hipotético) onde:
- O ator do departamento de Vendas calcula o valor do desconto de um pedido;
- O ator do departamento Financeiro calcula o valor da comissão a ser paga sobre um pedido;
public class Pedido
{
public List<ItemPedido> Itens { get; set; }
// Método utilizado pelo ator do departamento de Vendas
public decimal CalcularDesconto(decimal percentualDeconto)
{
decimal valorTotal = CalcularValorTotal();
decimal desconto = valorTotal * percentualDeconto;
return desconto;
}
// Método utilizado pelo ator do departamento Financeiro
public decimal CalcularComissao(decimal percentualComissao)
{
decimal valorTotal = CalcularValorTotal();
decimal comissao = valorTotal * percentualComissao;
return comissao;
}
// Método privado compartilhado entre os métodos
// CalcularImposto e CalcularComissao
private decimal CalcularValorTotal()
{
decimal valorTotal = Itens.Sum(item => item.Valor);
return valorTotal;
}
}
Independentemente se esses métodos estão em uma única classe ou alocados cada um em uma classe específica, esta tentativa de reaproveitamento de código faz com que os atores responsáveis pelo Financeiro e Vendas fiquem acoplados um ao outro, pois qualquer alteração na função CalcularValorTotal
atinge diretamente os métodos utilizados pelos dois atores.
Digamos que em algum momento futuro o ator do departamento des Vendas decida que não quer mais considerar itens do tipo Premium
no cálculo de descontos. O desenvolvedor prontamente decide incluir um filtro antes de somar os itens no método CalcularValorTotal
, sobe as alterações para o ambiente de homologação e pede para o ator do departamento de Vendas validar a alteração antes de subir para produção.
O ator do departamento de Vendas realiza todos os testes minuciosamente e indica que está tudo OK. Por fim, o desenvolvedor faz o deploy no ambiente de produção e finaliza a Change Request.
Dias (ou semanas) depois, alguém descobre que essa alteração quebrou o cálculo de comissão gerando um enorme problema para o ator do departamento Financeiro.
Isso por que nem fomos muito longe falando, por exemplo, sobre possíveis problemas no processo de merge das branches de outros desenvolvedores da equipe.
Tenho certeza que você já consegue imaginar a criticidade e o motivo pelo qual devemos evitar que atores estejam acoplados uns aos outros.
Dependendo do seu tempo na área de desenvolvimento, me arrisco a dizer que, muito provavelmente você já tenha vivenciado algum tipo de situação como esta.
Mas o destino quis que você chegasse até este ponto da leitura e agora você tem o poder nas mãos de simplificar o processo de desenvolvimento de software para você e todos os seus colegas de trabalho, evitando o famoso "arruma de um lado e estraga de outro".
Aplicando o SRP
Existem várias formas de evitar (e solucionar) este problema. Pensando a longo prazo e na saúde do software, poderiamos organizar o código da seguinte forma:
// Entidade de Pedido
public class Pedido
{
public List<ItemPedido> Itens { get; set; }
}
// Classe específica para o calculo de desconto de um pedido
// exclusiva para o ator do departamento de Vendas
public class ProvedorCalculoDescontoPedido(Pedido pedido)
{
public decimal Calcular(decimal percentualDesconto)
{
decimal total = pedido.Itens.Sum(item => item.Valor);
decimal desconto = total * percentualDeconto;
return desconto;
}
}
// Classe específica para o cálculo de comissão de um pedido
// exclusiva para o ator do departamento Financeiro
public class ProvedorCalculoComissaoPedido(Pedido pedido)
{
public decimal Calcular(decimal percentualComissao)
{
decimal total = pedido.Itens
.Where(item => item.Tipo != TipoItemPedido.Premium)
.Sum(item => item.Valor);
decimal comissao = total * percentualComissao;
return comissao ;
}
}
À partir de agora, qualquer nova solicitação de mudança realizada por qualquer um dos atores, não deverá mais gerar os problemas relatados anteriormente. Além disso, as classes estão mais coesas e agora são responsáveis por um, e apenas um, ator. Isso é lindo, não é mesmo?
Antes de ir embora, conta aí: você já conhecia o SRP por esta definição?
Obrigado pela sua atenção e espero que este artigo tenha sido útil para você.
Me siga para receber mais conteúdos como este. ❤️
Recomendação de leitura:
Arquitetura Limpa - O Guia do Artesão para Estrutura e Design de Software
Robert Cecil Martin, 2019.
Top comments (0)