Voltando no assunto do princípio da responsabilidade única, eu já havia dito para dar aos seus métodos uma única razão para mudar. Há um motivo a mais: acoplamento temporal.
Isso acontece quando você tem uma função que faz mais de uma coisa, ou seja, tem mais de um motivo para mudar.
Por exemplo, você tem uma classe produto, que tem uma propriedade “Preco”. Alguém chama o método “EntrarEmPromocao”. Esse método calcula o percentual de desconto com base em algum cálculo complexo, e aplica o desconto na propriedade “Preco”.
Pronto, você tornou sua classe mais difícil de testar. Como eu faço para testar somente o cálculo de desconto? Preciso configurar o preço antes? Ok, separo o cálculo:
void CriarDesconto() { //algum calculo, que no final seta: this.Preco -= algumValor; } public void EntrarEmPromocao() { CriarDesconto(); //faz outras coisas relacionadas à promoção }
Agora está separado. Mas o método CriarDesconto ainda faz muita coisa. Ok, então você separa mais. Cria um método “CriarDesconto”, e outro “AplicarDesconto”, assim:
private decimal _desconto; void CriarDesconto() { //algum calculo, que no final seta: _desconto = algumValor; } void AplicarDesconto() { this.Preco -= _desconto; }
Depois disso é só chamar no método EntrarEmPromocao:
public void EntrarEmPromocao() { CriarDesconto(); AplicarDesconto(); }
Ainda assim: como eu testo isso? Tenho que checar a variável privada _desconto? Complicado… Além disso, em que ordem as funções devem ser chamadas? Primeiro eu aplico, ou primeiro eu crio? Até faz algum sentido, mas comecem a imaginar isso em alguns domínios mais complexos…
O jeito é criar um acoplamento temporal, as classes são obrigadas a trabalhar juntas durante um tempo para obter o resultado desejado:
decimal ObterDesconto() { //algum calculo, que no final retorna: return algumValor; } void AplicarDesconto(decimal desconto) { this.Preco -= desconto; }
Pronto, não tem como chamar AplicarDesconto sem saber o valor do desconto. Tudo resolvido. A chamada fica clara, assim:
public void EntrarEmPromocao() { var desconto = ObterDesconto(); AplicarDesconto(desconto); }
Com isso, a função ObterDesconto é plenamente testável, e não tem mais efeitos colaterais.
Efeitos colaterais são coisas que acontecem quando você não esperava. Você chamava CriarDesconto e ela setava o valor do preço. Isso é um efeito colateral. Agora há responsabilidades claras. Um método obtem o desconto, o outro aplica. Posso testar separadamente.
Giovanni Bassi
Arquiteto e desenvolvedor, agilista, escalador, provocador. É fundador e CSA da Lambda3. Programa porque gosta. Acredita que pessoas autogerenciadas funcionam melhor e por acreditar que heterarquia é mais eficiente que hierarquia. Foi reconhecido Microsoft MVP há mais de dez anos, dos mais de vinte que atua no mercado. Já palestrou sobre .NET, Rust, microsserviços, JavaScript, TypeScript, Ruby, Node.js, Frontend e Backend, Agile, etc, no Brasil, e no exterior. Liderou grupos de usuários em assuntos como arquitetura de software, Docker, e .NET.