Princípio da Responsabilidade Única — SRP — SOLID

Kaique Prazeres
15 min readMar 31, 2021

--

Princípio da Responsabilidade Única, Clean Achitecture, Robert C. Martin

O princípio da reponsabilidade única é um princípio de “Design de Software” utilizado em conjunto com diversos paradigmas de programação. Mas, amplamente difundido no paradigma de programação orientada a objetos.

Também é conhecido como o primeiro dos princípios que compõem o acrônimo SOLID e como uma técnica de desenvolvimento de código que facilita a localização de mudanças e pontos de evolução.

Este principio foi identificado com base no trabalho de DIJKSTRA (1974), DEMARCO (1979), CONSTANTINE (1974), PAGE-JONES (1988) e PARNAS (1971), e parte da ideia de que cada módulo de software deve ter um, e apenas um, motivo para mudar.

Mais tarde, no final da década de 1990, o princípio foi consolidado como o “princípio da reponsabilidade única” pelo Robert C. Martin, mais conhecido como Uncle Bob.

E segundo ele, este é o princípio menos compreendido pela maioria dos profissionais que trabalham com desenvolvimento de software.

¹ Segundo o Robert C. Martin quando nos referimos a “Módulo” estamos nos referindo a um código fonte de uma classe, estrutura de dados ou conjunto de códigos coesos voltados para um contexto apenas. Não um conjunto de funcionalidades aleatórias que fazem referência indiretamente a uma classe.

O que diz o Princípio da Reponsabilidade Única

Um módulo deve ser responsável por um, e apenas um, ator.¹

Essa é a definição mais recente formulada pelo próprio Robert C. Martin em seu livro “Arquitetura Limpa”. Em outras palavras, devemos reunir as coisas que mudam pelas mesmas razões e separar coisas que mudam por diferentes razões. (Robert C. Martin 2020, Solid-Relevance)

O Objetivo

Escrever código simples.

De forma mais detalhada, o objetivo é atender as necessidades das pessoas do negócio garantindo que os papéis não se confundam no código-fonte, que haja apenas 1 motivo para mudança e que as regras de negócio não se misturem com regras técnicas de baixo nível como validações, consultas, SQL etc…

Por que aplicar?

Aqui, darei um resumo dos motivos que justificam o porque de aplicar o princípio da responsabilidade única. Mais a frente os detalharei como vantagens e desvantagens.

  • O código fica mais simples de manter.
  • As chances de causar bugs acidentalmente são reduzidas.
  • As funcionalidades ficam isoladas e organizadas.
  • O custo de manutenção é reduzido.
  • Facilita entender o código desenvolvido.

O que considerar antes de aplicar?

1º — Estudar. compreender bem toda a teoria que fundamenta o princípio, o contexto, e torná-lo um hábito ao modelar novas funcionalidades.

2º — Entender que este princípio não é uma bala de prata.
Assim como toda ferramenta, tecnologia, processo, metodologia, abordagem, técnica, arquitetura, este princípio não resolve todos os problemas. Apenas alguns deles.

3º — O princípio foi identificado por alguém que atua na área há muitos anos, possui muita bagagem em design de software e contribui muito com área. Isso nos mostra que o conceito e a prática não foram baseadas em puro achismo.

E que apesar deste princípio ter sido identificado por grandes nomes da área, não somos obrigados a aplicá-lo ou segui-los “cegamente”. Podemos optar por questioná-lo, criticá-lo, inclusive não aplicá-lo.

4º — Entender a importância de aplicar na prática um conhecimento comum entre boa parte dos desenvolvedores sérios e experientes que buscam simplificar seu trabalho em um desenvolvimento de software guiado por princípios bem fundamentados.

5º — Software está sempre evoluindo e precisa de uma manutenção fácil e intuitiva.

Vantagens

Facilidade de manter e evoluir requisitos

Como o princípio orienta que uma classe deve ser responsável por apenas um contexto de um ator, será mais fácil de identificar a qual contexto as regras de negócio pertencem.

Além disso, sua atenção no momento da manutenção estará altamente voltada para o que aquela classe faz naquele no contexto em que está inserida.

Isso ajudará você a não perder o foco tentando melhorar implementações relacionadas.

Facilidade de entender o código escrito

Mais clareza e objetividade no que precisa ser modificado, onde e como, pois a classe se refere apenas ao contexto de um ator. E isso reduz o seu esforço para entender como esse ator interage com os outros e as regras de negócio relacionadas e quando uma alteração “simples” precisa ser feita.

Facilidade de alterar o código com o menor esforço necessário

O código desenvolvido aplicando o princípio da responsabilidade é mais coeso, ou seja, faz o que diz que ela faz (é coerente), se conecta facilmente a outras funcionalidades. Logo, você precisará alterar apenas uma parte. E consequentemente sua entrega será mais rápida e sua produtividade aumentará.

Facilidade de Testar

Por ter um código com um nível de isolamento mais alto, a facilidade de criar testes será maior.

Facilidade de Reaproveitar

Por ter alta coesão e baixo acoplamento, no momento em que reaproveitar a funcionalidade, você precisará mudar poucos detalhes para adaptar ao novo contexto. Haverá pouquíssimas ou nenhuma dependência que não farão sentido de serem reaproveitadas.

Complexidade Reduzida e Legibilidade Melhorada

Por ter um código mais objetivo e focado em um ator, as chances de você entender o que o código faz com uma olhada rápida aumenta.

Acoplamento Reduzido e Coesão Aumentada

A dependência de outras funcionalidades é reduzida. O código faz exatamente o que ele diz que faz. Se você alterar uma parte dele, dificilmente quebrará outra parte relacionada.

Menor Duplicação e Fragilidade do Código

O código está em apenas um lugar. Você não precisará alterar a mesma coisa em outros lugares.

Maiores chances de evolução sem aumentar muito a complexidade

Se a essa altura você já tem facilidade de entender, reaproveitar e alterar o código com o menor esforço necessário as chances de evoluir sem aumentar demais a complexidade do código são maiores.

Desvantagens

Aumento do Número de Arquivos

A medida em que o principio é aplicado é possível que o número de arquivos aumente. E a depender de como você enxerga o aumento da quantidade de arquivos, pode ser um problema ter que lidar com isso a longo prazo.

Princípio da Responsabilidade Única na Prática

Os exemplos mostrados aqui terão um pouco mais de profundidade do que os exemplos superficiais que normalmente encontramos por ai.

1º Exemplo.

O diretor de marketing de uma empresa decide criar uma campanha digital em uma loja virtual para divulgar um novo produto que terá seu preço definido pelo diretor comercial, será vendido pelo vendedor e disponibilizados em um relatório para ser consultado pelo diretor financeiro.

  • O Diretor de Marketing deseja que seja possível criar campanha pelo sistema.
  • O Diretor Financeiro deseja que seja possível gerar o relatório do faturamento.
  • O Diretor Comercial deseja que seja possível gerenciar o produto.
  • O Vendedor deseja que a venda possa ser registrada e algumas validações sejam aplicadas no momento do registro.

Então, teremos a seguinte representação não aplicando o princípio da responsabilidade única:

Nota: Os exemplos deste artigo são escritos em C#. Por se tratar de uma linguagem orientada a objetos e ter uma sintaxe fácil de ser entendida.

Ao abstrair o mundo real para orientação a objetos alguns papéis se confundiram, a classe Venda ficou responsável por todos os atores e por regras de baixo nível como conversão de datas e acesso a banco de dados.

Exemplo de violação do princípio da responsablidade única em código C#

O código acima é um típico exemplo de violação desse princípio.

Os 4 atores possuem diferentes responsabilidades, mas no sistema eles são representados apenas por uma classe. E de quebra, temos mais duas responsabilidades: Lidar com persistência de dados e com conversão de data.

Existem contextos em que isso faz sentido, mas não é este o caso.

No mundo real o Vendedor é responsável por registrar a venda e dar descontos, no sistema, quando representado pela classe Venda, suas responsabilidades são criar campanhas, gerar relatório, definir preço de produto, registrar, salvar e consultar venda e converter datas para um formato específico.

No mundo real, de acordo com o nosso exemplo, o contexto foi traduzido corretamente?

“Ah, mas, tudo isso é relacionado a venda e o vendedor no final das contas”

Sim, tem relação com o Vendedor, mas ele não tem a responsabilidade de cuidar do que não diz respeito diretamente a ele. Pode ser que em alguns contextos sim, mas não neste. Além disso, outras solicitações partiram de outros atores, não somente do Vendedor.

Se uma nova funcionalidade for desenvolvida a partir da premissa ingênua de que a relação entre atores e contexto permite que mais responsabilidades sejam atribuidas a eles em um sistema, teremos um emaranhado de confusão em código.

Os efeitos práticos disso?

  • Abstração confusa que não representa fielmente as responsabilidades dos atores de negócio no mundo real.
  • Código bagunçado. Pois tem código de persistência, consulta de dados e conversão de datas misturados com regras de negócio.
  • Dificuldades no reuso, modularização e manutenibilidade.
  • Maior dificuldade no entendimento do código.

Os efeitos práticos acima são suficientes para dificultar a evolução do sistema. Em alguns casos podem até IMPEDIR que algum tipo de evolução aconteça.
Mas não é o nosso caso. Pois, temos uma solução!

Vamos a um exemplo mais realista de como isso pode acontecer ao longo do tempo na prática.

Em outro momento, as pessoas do negócio solicitam novas funcionalidades para o sistema.

  • O Diretor de Marketing deseja que seja possível veicular campanhas criadas em um período específico.
  • O Diretor Comercial deseja que seja possível criar promoção para o produto por um período específico.
  • O Vendedor deseja que seje possível dar descontos na venda.
Exemplo de violação do princípio da responsablidade única em código C#

Vamos assumir que esse número se multiplique ainda mais ao longo do tempo.

Facilmente essa será uma forte candidata a se tornar uma classe DEUS (Classe que faz tudo). E a facilidade de manutenção dela será minimizada.

Solução aplicando o Princípio da Responsabilidade Única

Antes de mostrar a solução, mostrarei algumas perguntas que realizei para chegar a uma solução para este contexto. O que facilitou a aplicação do principio de acordo com a definição.

As perguntas foram:

  • No mundo real, o Vendedor realmente tem todas essas responsabilidades que foram atríbuidas em sua representação no sistema?
  • O Vendedor confirma que todas essas tarefas são executadas por ele?
  • Há outros papéis na empresa que são responsáveis por algumas atividades que foram atribuídas ao Vendedor?
  • Quem são os outros atores que costumam solicitar mudanças?
  • Qual a fronteira do contexto de cada ator?
  • Existem responsabilidades com perfil mais técnico que normalmente são do próprio sistema?

As perguntas ajudaram a chegar na seguinte solução:

Classe: Venda
Ator: Vendedor

Exemplo de aplicação do princípio da responsablidade única em código C#

Classe: GerenciamentoProduto
Ator: Diretor Comercial

Exemplo de aplicação do princípio da responsablidade única em código C#

Classe: Faturamento
Ator: Diretor Financeiro

Exemplo de aplicação do princípio da responsablidade única em código C#

Classe: Divulgacao
Ator: Diretor de Marketing

Exemplo de aplicação do princípio da responsablidade única em código C#

Classe: VendaRepositorio
Ator: DBA

Exemplo de aplicação do princípio da responsablidade única em código C#

Classe: ConversorDeData
Ator: Sistema

Exemplo de aplicação do princípio da responsablidade única em código C#

Note que novos atores (DBA e Sistema) apareceram e cada um deles foi representado por uma classe que possui um conjunto de métodos que lidam com regras de baixo nível.

Isso foi feito porque regras de baixo nível como consultas de dados, conversão de datas possuem outras razões para mudar e não faz sentido misturá-las com regra de negócio.

Além disso, os novos atores foram incluídos com o intuito de representar ações que não dizem respeito aos atores que solicitaram as mudanças (Os Diretores e o Vendedor).

“Ah, mas agora que o princípio foi aplicado temos muitas classes”

Estamos trabalhando com orientação a objetos. Consequentemente o número de arquivos aumentarão a medida que o sistema for evoluindo e mais necessidades de negócio forem surgindo, além disso, na maioria das linguagens que adotam esse paradigma é recomendado ter uma classe por arquivo. E um dos benefícios é o isolamento.

E lembre-se que uma classe deve ter apenas 1 motivo para mudar e deve ser responsável somente por 1 ator, certo? Então, agora, quando houver motivo para mudança, ela será feita apenas em uma das classes.

Com a separação do contexto de cada ator, as responsabilidades de cada um deles no sistema não se misturam.

Diferente do exemplo de violação onde tínhamos uma classe DEUS.

Aproveito e deixo a seguinte provocação: Será que o crescimento do número de arquivos em softwares bem projetos não é algo inevitável? Reflita.

Atores e Papéis

Ter papéis associados as pessoas bem definidos é um desafio. Pois isso pode variar em cada contexto. Em uma empresa pequena por exemplo, 1 ator pode ter vários papéis, enquanto que em empresas maiores os papéis podem estar dissipados com responsabilidades bem definidas.

Mas ainda sim, é mais fácil pensar em pessoas realizando determinados papéis do que imaginar que uma pessoa faz todos. E assim, associamos o papel a cada ator.

E com isso temos os motivos para mudanças bem definidos.

O princípio da responsabilidade única trata de reunir coisas que mudam pelas mesmas razões e de separar coisas que mudam por diferentes razões.
(Uncle Bob, SOLID Relevance)

Por estes motivos, temos uma série de classes isoladas no sistema representando cada coisa do mundo real.

A Confusão nas interpretações

Em sua primeira definição, o princípio dizia que:

Uma classe deve ter um, e apenas um, motivo para mudar.

E é comum encontrar desenvolvedores que interpretaram essa definição da seguinte forma:

Cada classe deve ter um, e apenas um método.

Mas não é isso que o princípio diz. Pois a definição mais recente deixa mais claro e com menos margem para esse tipo de interpretação.

Um módulo deve ser responsável por um, e apenas um, ator.

Motivos para mudanças, sua origem e responsabilidade

Mas o que são esses motivos para mudança e de onde eles vem?

Os motivos para mudanças vem das pessoas da área de negócio e surgem a partir das necessidades dessas pessoas ou do contexto que estão inseridas.

Seguindo esse raciocínio, imagine que uma dessas pessoas perceba que o sistema da empresa precise de alguma adequação em alguma funcionalidade. Essa pessoa entende que isso precisa acontecer, então, ela solicita a mudança a partir dessa necessidade.

Essa necessidade é atendida por alguma representação no sistema. E essa representação no sistema possui responsabilidade sobre as mudanças que o ator solicitou.

Então, uma responsabilidade é um conjunto de funções que serve a um ator em particular. (Robert C. Martin)

Por que…

Se uma classe tem mais de uma responsabilidade, as responsabilidades se tornam acopladas. Mudanças em uma responsabilidade podem prejudicar ou inibir a capacidade da classe de cumprir as outras. Esse tipo de acoplamento leva a projetos frágeis que estragam de maneiras inesperadas quando alterados
(Robert C. Martin 2002)

Continuando com o exemplo…

Ok, o Princípio da Responsabilidade Única foi aplicado seguindo a sua definição mais recente.

Observando o exemplo apresentado, conseguimos extrair os seguintes pontos:

  • Ficou mais fácil entender quais são as responsabilidades de cada classe.
  • Os contextos estão separados e limitados por autor, logo a organização aumentou.
  • A coesão de cada classe está maior, pois a responsabilidade dela é direcionada exclusivamente ao contexto de apenas 1 ator.
  • O acoplamento está mais baixo pois o número de dependências entre os processo foi isolado.
  • Ficou mais fácil de dar manutenção. Pois é fácil saber qual a responsabilidade de cada classe, além disso é mais simples reutilizar cada uma delas sem ter que levar todas as dependências junto ou código desnecessário .
  • Separamos as regras de negócio das regras de persistência, consulta de dados e dos detalhes da conversão de datas.
  • Ficou mais fácil entender por quais motivos as mudanças acontecem e onde elas devem acontecer

Respostas para dúvidas comuns

  • Uma classe deve ter somente um método?
    Não. Ele pode ter outros métodos privados, públicos ou protegidos, desde que façam parte do mesmo contexto e tratem de comportamentos do mesmo ator.
  • Uma classe pode ser responsável por mais de 1 ator?
    A definição do princípio diz que não. Mas, aplicar o princípio criando exceções e desconsiderando a própria definição é o mesmo que não aplicá-lo.
  • É obrigatório aplicar o Princípio da Responsabilidade única?
    Não. O princípio não é uma lei. Se um dos objetivos não é facilitar a manutenção, evolução, melhoria e entendimento não precisa aplicar. Ou até mesmo, senão quiser, não precisa aplicar.
  • Regras de validação ou consultas SQL também podem ser responsabilidades da mesma classe?
    Não. Alias, esse também é um dos motivos desse princípio existir. Separar regras de baixo nível das regras de negócio.
  • Este princípio só pode ser aplicado com orientação a objetos?
    Não necessariamente. Há quem diga que não é possível aplicar os princípios SOLID sem orientação a objetos e há quem defenda o contrário. É um assunto que cabe uma grande discussão, pois dependendo do contexto ou tecnologias envolvidas, este princípio pode ser aplicado de uma forma diferente do convencional.

As Críticas e Questionamentos ao Princípio da Responsabilidade Única

Como dito anteriormente, apesar do princípio ter sido identificado por grandes nomes do desenvolvimento de software, ele também está sujeito a críticas e questionamentos.

Pois, não são leis que devemos seguir cegamente.

Diante disso, faço algumas considerações a respeito.

Definição Ambígua e Abertura para interpretações variadas

As definições (Primeira e mais recente) deste princípio passaram uma ideia vaga sobre o que significa responsabilidade única e gerou muita confusão sobre como aplicá-lo.

Responsabilidade significa atribuição e unicidade, representa apenas 1 coisa.
Quando associamos a primeira definição com “classes” a ideia que é passada sobre fazer uma coisa, ter uma única responsabilidade, é de que a classe deve ter apenas um método, e não um contexto.

Esse tipo de abertura na interpretação, consequentemente forçou a atualização da definição (Acredito eu).

E o problema disso é que o tornou mais um daqueles conceitos do mundo da programação que a maioria dos desenvolvedores interpretam de forma simplista e com a profundidade de um pires. E para piorar, utilizam exemplos reducionistas e mal elaborados para exemplificar como funciona na prática.

Verbosidade

O princípio da responsabilidade única de certa forma incentiva a criação de mais componentes, e a longo prazo isso significa ter mais arquivos. O que para alguns pode tornar a manutenção mais complicada.

No entanto, isso também é uma consequência da evolução de todo software que um dia se tornará grande e precisa ter uma organização mais elaborada.

O aumento do número de arquivos é inevitável, exceto se tudo for feito em uma quantidade especificada de arquivos ou que tudo seja feito em apenas um.

Mas imagina se levarmos a primeira definição ao pé da letra? O que acontece? Milhares de classes com apenas 1 método.

Responsabilidade não é única com a Herança

Outro ponto que chama atenção é a herança. Em casos que se faz necessário o uso dela, a palavra “única” acaba perdendo um pouco do sentido. Pois, uma classe que herda características e comportamentos passa a ter mais responsabilidades além das suas, só que através de um tipo de associação.

Para facilitar, pense no clássico exemplo das classes Pessoa, PessoaFisica e PessoaJuridica.

Onde Pessoa é a abstração Base que terá derivações representadas por PessoaFisica e PessoaJuridica através da herança. É a forma de associação mais comum de ser utilizada pela maioria dos desenvolvedores.

Agora pensa comigo. A responsabilidade da pessoa física é representar o indivíduo enquanto sujeito detentor de direitos e de deveres. Enquanto que a classe pessoa tem a responsabilidade de representar o ser humano, uma criatura, etc..

Ambas as pessoas compartilham algumas responsabilidades, tanto no mundo real, quanto no código normalmente. Até ai quase tudo certo.

Mas e no caso da pessoa jurídica quando herda da classe Pessoa?

Ela herda caracteristicas e responsabilidades que não são de uma pessoa jurídica (empresa).

E a herança, consequentemente quebra o princípio em muitos contextos.

Diante dessa perspectiva, a responsabilidade acaba nem sempre sendo única. Se isso viola o princípio da responsabilidade única do SOLID, neste caso, o que consideramos? rsrs, não é meio contraditório?

Um termo novo para falar sobre Coesão e Acoplamento

Me questiono o por que anos depois um princípio é criado dizendo as mesmas coisas que dois dos princípios GRASP (Coesão e Acoplamento), só que de forma ambígua em uma das definições e aberta a variadas interpretações.

Coesão e Acoplamento são conceitos descritos pelo Larry Constantine no IBM Systems Journal em 1974.

Não seria mais simples enfatizar a importância desses conceitos ao invés de criar um novo inicialmente confuso?

Tudo isso faz com que muitos desenvolvedoras tenham dificuldade de entender a importância do princípio e como ele deve ser aplicado.

Principalmente os iniciantes e os que resolvem se aventurar com Design e Arquitetura de Software.

1 Motivo para mudança. Na prática faz sentido?

Outro ponto que é bem relevante é o motivo da mudança.

Quando se trata de negócio, existem vários motivos para um sistema mudar. Inclusive suas entidades (módulo, classe ou método).

Mas, como é possível ter UM motivo para mudança se além das regras de negócio é possível que uma validação necessite de modificação, uma consulta, ou condição seja melhorada ou removida? Esse UM motivo consequentemente não desencadeia mais motivos?

Pense nisso…

Conclusão

Este princípio descreve uma forma de representar o negócio de forma coerente em um sistema, facilitar a manutenção e como dito anteriormente: Se refere as necessidades dos atores de negócio seus motivos para mudanças.

Apesar de algumas confusões em sua interpretação, ele orienta uma maneira de escrever código simples guiado por princípios que restringem os programadores de produzir códigos ruins e confusos.

Mas como tudo na vida, ele também tem suas vantagens e desvantagens. Que precisam ser consideradas para entender se faz ou não sentido aplicá-lo em em alguns contextos. Apesar de que, de uma geral, na maioria dos contextos se fazem necessárias a aplicação dele.

No entanto, aplicá-lo “parcialmente” a partir de “exceções”, é o mesmo que não aplicá-lo e fugir da simplicidade que ele propõe.

E já que há muitos benefícios, por que não aplicar?

Espero ter ajudado a compreender.

Aguardo por críticas, sugestões e uma boa discussão sobre este princípio.

Referências

Robert C. Martin, Clean Architecture. Single Responsability Principle. 2017

ArticleS.UncleBob.PrinciplesOfOod (butunclebob.com)

Robert C. Martin, Solid Relevance, 2020. Clean Coder Blog

Robert C. Martin, Single Reponsibility Principle, Clean Coder Blog

Stevens, Wayne P.; Myers, Glenford J.; Constantine, Larry LeRoy (June 1974). “Structured design”. IBM Systems Journal.

DIJKSTRA, Edsger W. On the role of scientific thought. Burroughs Research Fellow, Netherlands, 1974.

MARTIN, Robert C. The Single Responsibility Principle. 2014

--

--

No responses yet