O Princípio da Responsabilidade Única

Single Responsibility Principle — SRP

Ricardo Dias
Contexto Delimitado
10 min readSep 20, 2019

--

Este princípio diz respeito às necessidades das pessoas e à organização do conhecimento. O primeiro passo para usar esse princípio, é entender o que significa “responsabilidade” no contexto de um software.

A Responsabilidade Geral

Um Design que comunica informação

Gosto muito da forma como EVANS(2016) sugere o desenvolvimento de um aplicativo de sucesso. Embora tenha publicado seu catálogo de práticas somente em 2016, vários conceitos abordados por ele já eram usados de forma avulsa por projetistas de várias partes do globo. Em seu livro EVANS(2016) afirma que os “modeladores de software eficazes digerem conhecimento e … a medida que o modelo avança, torna-se uma ferramenta para organizar as informações que continuam a fluir pelo design”.

O design de um software deve conversar com o programador e exalar conhecimento do negócio

Os modeladores, ou seja, os projetistas do software têm a tarefa de organizar o software de tal forma que ele se torne o mais auto-explicativo possível. Em outras palavras, o código fonte e a maneira como ele é organizado devem “contar a história do software”, revelando informações que fazem sentido e facilitam seu entendimento. O código fonte deve conversar com o desenvolvedor sobre as regras de negócio implementadas.

Responsabilidade: a forma como o software é desenhado é responsável por emitir as informações que aumentarão o conhecimento do programador sobre o sistema.

Um Design organizado

A base para um bom software é a organização, que pode ser feita separando as funcionalidades em grupos distintos aos quais chamamos de módulos.

CONSTANTINE (1974) explica que a palavra módulo é um “termo usado para se referir a um conjunto de uma ou mais instruções de programa contíguas com um nome pelo qual outras partes do sistema podem invocá-lo e, de preferência, com seu próprio conjunto distinto de nomes de variáveis.

Módulos como bibliotecas independentes são ótimos exemplos de coesão

Estando dentro de um sistema (como parte do projeto) ou fora (como uma biblioteca), o módulo deve ser implementado da forma mais independente possível. Deve-se decompor uma gama de funcionalidades, de maneira que sejam agrupadas e nomeadas, separando-as de acordo com seu contexto. Em linguagens orientadas as objetos, usa-se os namespaces ou pacotes para isso. Parece ser uma tarefa fácil, mas não é.

Segundo PARNAS (1971), “geralmente é incorreto iniciar a decomposição de um sistema em módulos com base em um fluxograma. Em vez disso … comece com uma lista de decisões de design que sejam difíceis ou que provavelmente mudarão. Um módulo deve ser projetado para ocultar tal decisão dos outros”. Se o módulo for de uma classe só, isso significa que uma classe deve ocultar suas “decisões” das outras classes, pois cada uma deve ter sua própria responsabilidade.

Esse conceito se completa com a prática da Separação de Interesses (ou Separation of Concerns - SOC como é conhecida), que consiste em agrupar os módulos (arquivos e classes) de forma que sejam reconhecidos por suas respectivas obrigações ou estados de mudança. Ou seja, as partes que mudam pouco devem ser agrupadas em um lugar (as bibliotecas são exemplos de módulos desse tipo), enquanto aquelas que mudam muito devem ser agrupadas em outro (regras de negócio, por exemplo).

DIJKSTRA (1974) afirma que “qualquer coleção estranha de fragmentos de conhecimento … não constituem uma restrição científica. Para que a separação seja significativa, temos também um requisito interno e um externo. O requisito interno é de coerência: o conhecimento deve apoiar as habilidades e as habilidades devem permitir-nos melhorar o conhecimento. O requisito externo: … quanto mais auto-sustentável o sub-universo intelectual, menor será a necessidade de detalhar o conhecimento para seus praticantes … e maior será sua viabilidade”.

Resumindo, é uma via de mão dupla:

  • O conhecimento do programador deve ajudar a construir o software;
  • As informações expostas na organização do código fonte do software deve ajudar o programador a obter maior conhecimento.

Responsabilidade: a forma como o código fonte é separado e agrupado dentro do projeto é responsável por emitir as informações que aumentarão o conhecimento do programador sobre o sistema como um todo.

Um Design coeso

As décadas de 1970 e 1980 foram um período fértil para os princípios da arquitetura de software. Durante esse período, as noções de Acoplamento e Coesão foram introduzidas por vários autores na literatura técnica.

Um dos pioneiros a falar sobre o assunto foi CONSTANTINE (1974). Segundo ele o acoplamento surge da “técnica amplamente usada de aproveitar áreas de dados comuns (ou variáveis ​​globais ou módulos sem seu próprio conjunto distinto de nomes de variáveis) que pode resultar em um número enorme de conexões entre os módulos de um programa. A complexidade de um sistema é afetada não apenas pelo número de conexões, mas pelo grau em que cada conexão acopla (associa) dois módulos, tornando-os interdependentes e não independentes”.

A atitude de tentar aproveitar o máximo possível do código em todos os lugares do sistema deve ser feita com cautela e sensatez. Aproveitamento é importante, mas a coesão e facilidade é muito mais! É preciso saber quando e como aproveitar código, para não transformar o projeto em um emaranhado infinito de conexões onde mudar uma coisa afeta outras dez.

Um código fonte sem coesão é difícil de manter sem quebrar outras partes do sistema

Para solucionar esse problema, CONSTANTINE (1974) propõe a coesão explicando que minimizar as conexões entre os módulos também minimiza os caminhos pelos quais alterações e erros podem se propagar para outras partes do sistema, eliminando efeitos desastrosos de ‘ondulação’, onde alterações em uma parte causam erros em outra, exigindo alterações adicionais em outros lugares e gerando novos erros”.

Módulos bem projetados são coesos, ou seja, fazem sentido para quem quiser entendê-los. Também são independentes, mesmo estando dentro do núcleo do sistema. Trabalhar de forma modular é a melhor saída para coesão e reaproveitamento de código para outros projetos.

Responsabilidade: a forma como cada módulo é construído, separado dos demais, é responsável por emitir as informações que aumentarão o conhecimento do programador sobre o macro-contexto de cada módulo específico.

A Responsabilidade Única

No final dos anos 90, MARTIN (2014) consolidou as noções sobre responsabilidade (abordados até agora) em um princípio específico, que chamou de Single Responsability Principle (SRP) ou Princípio da Responsabilidade Única.

No Princípio de Responsabilidade Única regulamenta-se que:

Cada módulo de software deve ter um e apenas um motivo para mudar!

Olhando para a declaração de PARNAS (1971), isso parece alinhar-se perfeitamente. Todavia, surge a pergunta: o que é um motivo para mudar?

O que NÃO é um “motivo para mudar”

Em um software real, as mudanças acontecem o tempo todo e isso é bom! Um software flexível e com design bem feito propicia que mudanças aconteçam e sejam fáceis de manter. Neste sentido, o leitor pode pensar em alguns motivos como:

  • O cliente pediu para mudar uma tela;
  • A versão da linguagem atualizou-se, necessitando mudar algumas coisas;
  • A correção de um BUG obrigou-nos a mudar o código.

A questão é que o “motivo para mudar”, ao qual Martin se refere, não diz respeito às evoluções ou correções de bugs que todo software precisa para continuar funcionando. Se fosse assim, seriam sempre vários motivos para mudar e o princípio seria impossível de cumprir.

O que é um “motivo para mudar”

O princípio se refere a uma questão de design, ou seja, de como uma funcionalidade pode ser organizada para que tenha uma única responsabilidade e seja fácil de entender e de manter.

MARTIN (2014b) explica que “No contexto do Princípio da Responsabilidade Única, a ‘responsabilidade’ é sinônimo de ‘motivo de mudança’. Se você olhar para uma classe e encontrar mais de um motivo para mudá-la, essa classe tem mais de uma responsabilidade”.

Para facilitar a compreensão, vamos transportar a realidade de uma empresa para dentro do design de um software. Suponha que em uma determinada empresa exista a seguinte estrutura de obrigações:

  • Diretoria: são funcionários responsáveis pelas estratégias da empresa e também pela contratação e demissão de funcionários;
  • Depto. Financeiro: os funcionários deste departamento são responsáveis pelo controle das finanças na empresa;
  • Depto. de Operações: os funcionários deste departamento são responsáveis pela organização operacional, incluindo controle das horas trabalhadas;
  • Depto. de TI: os funcionários desse departamento são responsáveis pela manutenção da tecnologia dentro da empresa, gerindo bancos de dados, computadores, redes etc.
Estrutura de departamentos em uma empresa fictícia

Perceba que cada departamento tem uma responsabilidade única e específica. Na hora de fazer um sistema computacional, os projetistas desenvolvem uma classe chamada Funcionário contendo três métodos:

  • calcularPagamento: faz o o cálculo do salário de um funcionário para o Depto. Financeiro efetuar o pagamento do mês;
  • reportarHoras: devolve um relatório das horas trabalhadas e das horas extras para o Depto. de Operações conferir;
  • salvarDados: salva os dados de pagamento no banco de dados que é gerido pelo Depto. de TI.
Funcionalidades acopladas na classe Funcionário

Perceba que a classe Funcionário possui três responsabilidades:

  • calcularPagamento: responsabilidade do Depto. Financeiro;
  • reportarHoras: responsabilidade do Depto. de Operações;
  • salvarDados: responsabilidade do Depto. de TI;

A classe Funcionário possui responsabilidades demais, o que a torna altamente acoplada!

Imagine que o Depto. Financeiro solicite uma mudança na porcentagem para cálculo de horas extras. A mudança aconteceria na classe Funcionário.

E se o Depto. de TI solicitar uma mudança na forma como os dados são salvos no banco de dados? A mudança aconteceria também na classe Funcionário.

E se o Depto. de Operações pedir para acrescentar no relatório, além do reporte de horas extras, um abono diferente para quem trabalhar no feriado? A mudança aconteceria também na classe Funcionário.

Ou seja, a classe Funcionário tem três motivos para mudar, o que, consequentemente, acrescenta três motivos para gerar falhas. Se uma coisa der errado em uma responsabilidade, poderá afetar as outras duas, deixando os três departamentos sem funcionar. Isso é desastroso!

MARTIN (2014b) sugere uma comparação para exemplificar a gravidade dessa situação: “Imagine que você levou seu carro a um mecânico para consertar uma janela elétrica quebrada. Ele liga para você no dia seguinte dizendo que está tudo consertado. Quando você pega o carro, a janela funciona boa; mas o carro não liga. É improvável que você volte a esse mecânico porque ele é claramente um idiota. É assim que os clientes e gerentes se sentem quando quebramos as coisas de que eles se importam e que não nos pediram para mudar”.

Martin vai além: “É por isso que não colocamos SQL em JSPs. É por isso que não geramos HTML nos módulos que calculam resultados. Esse é o motivo pelo qual as regras de negócios não devem conhecer o esquema do banco de dados. Esta é a razão pela qual separamos as preocupações”.

Para resolver o problema do acoplamento, o Princípio da Responsabilidade Única sugere uma abordagem que propicie a coesão:

Reúna as coisas que mudam pelas mesmas razões. Separe as coisas que mudam por diferentes motivos.

No mesmo cenário que usamos, vamos desfazer o acoplamento (onde tudo é responsabilidade da classe Funcionário) e produzir classes coesas, contendo cada uma sua própria responsabilidade:

Funcionalidades coesas usando Separação de Interesses (SOC)
  • FuncionárioFinanceiro: esta classe só faz o calculo do pagamento;
  • FuncionárioOperações: esta classe só devolve o relatório;
  • FuncionárioTI: esta classe só salva no banco de dados;

Agora, se uma mudança precisar acontecer no método cálcularPagamento, apenas a classe FuncionárioFinanceiro será mudada. O melhor disso é que se uma falha acontecer em FuncionárioFinanceiro, o relatório continuará funcionando e o dados continuarão sendo salvos no banco de dados.

Conclusão

Como dito no início deste artigo, o Princípio da Responsabilidade Única diz respeito às necessidades das pessoas. Essas necessidades são as razões para haver mudanças no código fonte de um software.

Da mesma maneira que você não pode confundir os papéis das pessoas envolvidas no negócio (perguntar sobre TI para alguém do Financeiro), também não pode confundir os papéis das classes no código fonte (implementando regras de TI na classe que trata finanças).

Por enquanto é isso. Espero que o conteúdo esteja sendo útil. Até a próxima!

Leia também o próximo artigo sobre o assunto:

Leia todos os artigos desta série:

Referências para Aprofundamento

CONSTANTINE, Larry L. Structured design. IBM Systems Journal, VOL13, NO 2, 1974.

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

EVANS, Eric. Domain-Driven Design, Atacando a Complexidade no Coração do Software. Edição: 3ª. Alta Books, Rio de Janeiro, 2016

MARTIN, Robert C. The Single Responsibility Principle. 2014. Disponível em <https://blog.cleancoder.com/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html>. Acesso em 01/09/2019

MARTIN, Robert C. The Single Responsibility Principle. 2014b. Disponível em <http://www.butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod>. Acesso em 01/09/2019

PARNAS, David L. On the Criteria To Be Used in Decomposing Systems into Modules. Carnegie-Mellon University, 1971

--

--

Ricardo Dias
Contexto Delimitado

Apaixonado por padrões, programação clara, elegante e principalmente manutenível. Trabalha como desenvolvedor deste 2000, incrementando a cada ano este loop…