Desvendando o SOLID: Single Responsibility Principle

Vitor Ferraz Varela
5 min readMar 22, 2021

--

Introdução

Hoje vamos falar sobre o famoso SOLID, que é um acrônimo de cinco princípios da programação orientada a objetos e design de código, identificados por Robert C. Martin (ou para os íntimos Uncle Bob), em um artigo chamado Design Principles and Design Patterns por volta do ano 2000.

A ideia base desses princípios, é ajudar a pessoa desenvolvedora a escrever códigos mais coesos, separando responsabilidades e diminuindo o acoplamento.

Nessa artigo, irei abordar o primeiro desses princípios: Princípio da responsabilidade única (SRP — Single Responsibility Principle) e de que forma se relaciona com a coesão do nosso código.

A coesão e SRP (Single Responsibility Principle)

Bom, primeiro vamos entender o que é essa tal de coesão, que na verdade não é algo tão complicado quanto parece.

Uma classe coesa, é aquela que possui uma única responsabilidade. Ou seja, ela não toma conta de mais de um conceito no sistema, possuindo responsabilidades bem definidas.

Mas de que forma, ter várias responsabilidades dentro de uma classe pode influenciar negativamente no nosso código?

Tudo começa com aquela famosa frase do “só mais uma feature aqui” e pronto, acabamos colocando novas responsabilidades em uma classe que não faz sentido.

Existe um jargão no mundo iOS, que é a famosa Massive View Controller, na qual acabamos colocando tudo nela: layout, chamadas para APIs, fluxo de navegação e várias regras de negócio.

Desta forma, rapidamente nossa classe vai perdendo a coesão e se tornando cada vez mais acoplada com diversas partes do nosso sistema e se tornando mais difícil de se dar manutenção.

Uma classe não coesa ainda têm outro problema: é fácil causar efeitos secundários ou que uma regra influencie a outra e assim um defeito seja propagado para outras classes. Desta forma temos um efeito bola de neve e esses problemas acabam ficando cada vez maiores.

Exemplo de uma classe não coesa

Imagine o seguinte cenário de uma calculadora de salários, um exemplo bem clichê mas não deixa de ser um ótimo exemplo. Ela é responsável por calcular os salários dos funcionários. A regra é bem simples: de acordo com o cargo e o salário, podemos aplicar um desconto diferenciado.

Repare que cada uma das regras é implementada por um método privado, como o quinzeOuVintePorcento ou dezOuVintePorcento.

Acredito que em algum momento ja encontramos um cenário bem parecido com este. O problema acaba acontecendo quando temos que adicionar novas regras de acordo com um cargo ou desconto, e assim seria necessário adicionar novos ifs ou métodos novos para tratar cada um desses cenários. Quando encontramos um cenário desses, o ideal seria tentar criar uma solução mais genérica.

Nesse caso por exemplo, podemos notar que existem 3 cargos (desenvolvedor, gerente e DBA) com regras bem similares. Claro que esse exemplo contém poucos casos para fins didáticos, um cenário real seria bem mais complexo. Desta forma tentar enquadrar a solução acima em um cenário real, deixaria nossa classe nada coesa.

Repare, toda classe que é não coesa não para de crescer nunca.

No caso acima temos outro problema, imagine que por algum motivo exista a necessidade de usar a regra de quinzeOuVintePorcento em outro lugar do nosso código. Seria necessário duplicar essa regra em lugares distintos e caso tenha mudanças, lembrar de alterar em dois lugares.

Dar manutenção em cenários assim se torna uma tarefa muito complicada, além disso, sempre pode existir a chance de causar efeitos colaterais durante as alterações. Propagando o problema para outras classes dentro do nosso sistema.

Como encontrar a coesão ?

Um ponto importante importante foi entender o porque essa classe não está coesa. A classe CalculadoraDeSalario não para de crescer por dois motivos: sempre que um cargo novo surge ou sempre que uma nova regra de cálculo aparece precisamos alterar a classe. Tendo em vista esses cenários, vamos começar isolando a regra de cálculo.

Cada regra fica contida dentro de um método privado que possui um esqueleto base, de acordo com o tipo de funcionário é feito um cálculo. Esse esqueleto é essencial para criamos a nossa abstração da regra de cálculo em um protocolo.

Desta forma, podemos criar uma classe que implementa cada uma das regras existentes. Assim cada classe que implementa esse protocolo possui somente uma regra e portanto se torna muito mais coesa.

Responsabilidades separadas, classes menores se tornam mais fáceis de serem mantidas

Por fim nossa classe CalculadoraDeSalario ficaria da seguinte forma:

Se implementarmos dessa forma, repare que cada regra de cálculo agora está bem isolada, isto é, será bem difícil que uma mudança em uma das regras afete a outra regra. Cada classe contém apenas uma regra, fazendo essa classe muito coesa, afinal ela só mudará se aquela regra em particular mudar. Além disso por separar essa regras de cálculo em classes separadas, de cara ganhar a possibilidade de fazer o reuso dessas regras em outros cenários.

Um questionamento que acho importante ressaltar, antes essas regras estavam em métodos maiores e privados. Talvez separar a complexidade em métodos menores seja uma alternativa, mas se quebrarmos um método grande em vários pequenos métodos privados, não teremos reuso daquele comportamento isolado, como mencionado anteriormente.

Métodos privados são excelentes para melhorar a legibilidade de um método maior. Se você percebeu que existem duas diferentes responsabilidades em uma mesma classe, separe-os de verdade.

Métodos privados servem de apoio aos métodos públicos. Eles ajudam a aumentar a legibilidade do nosso código. Mas lembre-se: se seu método público crescer muito ou sofrer alterações constantes, talvez valha a pena extraí-lo para uma classe específica.

Por fim podemos associar cada uma das regras em existe ao enum de cargos:

Dessa forma, qualquer novo cargo deverá, obrigatoriamente, passar uma regra de cálculo. Claro isso é um design de implementação que criamos, podem existir outros, mas o principal é entender o problema e de que forma conseguimos encapsular-lo melhor, para que a mudança, quando necessária, seja feita em um único ponto e se propague naturalmente, sem necessidade de alterações em outras classes.

Nossa classe de calcular salários no final ficaria desta forma:

Conclusão

O SRP, ou Single Responsibility Principle, é justamente o princípio que nos lembra de coesão. Esse princípio nos diz que a classe deve ter uma, e apenas uma, razão para mudar.

É realmente difícil enxergar a responsabilidade de uma classe. Talvez essa seja a maior dúvida na hora de escrever um código com coesão. Minha sugestão é procurar por classes que são modificadas com frequência: que não param nunca de crescer; identifique um esqueleto base para o vários métodos dentro dessa classe. Comece a pensar em dividir essas responsabilidades em classes menores.

Escrever um código coeso e de qualidade é um desafio constante e incremental, dificilmente conseguimos acertar de primeira. Com o tempo se torna uma tarefa mais fácil, você modela, observa seu modelo e com ele melhora.

Mas no final, tudo é um questão de separação. Se separarmos bem nosso código, ele será muito mais fácil de ser mantido e estendido.

Muito obrigado por acompanhar até aqui e me siga no Medium para não perder os próximos artigos.

Vocês podem me encontrar nas redes sociais:

Linkedin | Instagram | Twitter

--

--