Garanta que cada classe tenha apenas uma responsabilidade, por quê?


37

De acordo com a documentação da Microsoft, o artigo de princípio do Wikipedia SOLID, ou a maioria dos arquitetos de TI, devemos garantir que cada classe tenha apenas uma responsabilidade. Gostaria de saber por que, porque se todos parecem concordar com essa regra, ninguém parece concordar com os motivos dessa regra.

Alguns citam melhor manutenção, outros dizem que ele fornece testes fáceis ou torna a classe mais robusta ou de segurança. O que é correto e o que realmente significa? Por que torna a manutenção melhor, os testes mais fáceis ou o código mais robusto?



11
Também tive uma pergunta que você pode achar parecida: Qual é a real responsabilidade de uma classe?
Pierre Arlaud

3
Todas as razões da responsabilidade única não podem estar corretas? Facilita a manutenção. Isso facilita o teste. Isso torna a classe mais robusta (em geral).
Martin York

2
Pela mesma razão, você tem aulas.
Davor Ždralo

2
Eu sempre tive um problema com esta declaração. É muito difícil definir o que é uma "responsabilidade única". Uma única responsabilidade pode variar de "verificar se 1 + 1 = 2" a "manter um registro preciso de todo o dinheiro pago dentro e fora das contas bancárias corporativas".
Dave Nay

Respostas:


57

Modularidade. Qualquer linguagem decente fornecerá os meios para colar partes de código, mas não há maneira geral de descolar uma grande parte de código sem que o programador faça uma cirurgia na fonte. Ao colocar várias tarefas em uma construção de código, você aproveita a oportunidade de combinar suas partes de outras maneiras e introduz dependências desnecessárias que podem causar alterações em uma parte e afetar as outras.

O SRP é tão aplicável às funções quanto às classes, mas as principais linguagens OOP são relativamente pobres em colar funções.


12
+1 por mencionar a programação funcional como uma maneira mais segura de compor abstrações.
precisa

> mas as principais linguagens OOP são relativamente pobres em colar funções. <Talvez porque as linguagens OOP não estejam preocupadas com funções, mas com mensagens.
Jeff Hubbard

@JeffHubbard Eu acho que você quer dizer que é porque eles pegam C / C ++. Não há nada sobre objetos que os tornem mutuamente exclusivos com funções. Inferno, um objeto / interface é apenas um registro / estrutura de funções. Fingir que as funções não são importantes é injustificado, tanto na teoria quanto na prática. Eles não podem ser evitados de qualquer maneira - você acaba tendo que usar "Runnables", "Fábricas", "Provedores" e "Ações" ou fazer uso dos chamados "padrões" do Método de Estratégia e Modelo e acabar com um monte de clichês desnecessários para fazer o mesmo trabalho.
Doval

@Doval Não, eu realmente quero dizer que OOP está preocupado com mensagens. Isso não quer dizer que uma determinada linguagem seja melhor ou pior que uma linguagem de programação funcional para colar funções juntas - apenas que essa não é a principal preocupação da linguagem .
Jeff Hubbard

Basicamente, se o seu idioma é tornar o POO mais fácil / melhor / mais rápido / mais forte, então se as funções estão ou não juntas, não é o que você focaliza.
Jeff Hubbard

30

Melhor manutenção, teste fácil, correção mais rápida de erros são apenas resultados (muito agradáveis) da aplicação do SRP. O principal motivo (como Robert C. Matin coloca) é:

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

Em outras palavras, o SRP aumenta a mudança de localidade .

SRP também promove código DRY. Contanto que tenhamos classes com apenas uma responsabilidade, poderemos optar por usá-las em qualquer lugar que desejarmos. Se temos uma classe que tem duas responsabilidades, mas precisamos apenas de uma delas e a segunda está interferindo, temos 2 opções:

  1. Copie e cole a classe em outra e talvez até crie outro mutante de várias responsabilidades (aumente um pouco a dívida técnica).
  2. Divida a classe e faça-a como deveria ser, em primeiro lugar, o que pode ser caro devido ao uso extensivo da classe original.

11
+1 Para vincular SRP a DRY.
Mahdi

21

É fácil criar código para corrigir um problema específico. É mais complicado criar código que corrige esse problema e permite que alterações posteriores sejam feitas com segurança. O SOLID fornece um conjunto de práticas que aprimoram o código.

Quanto a qual deles está correto: Todos os três. Todos são benefícios do uso de responsabilidade única e o motivo pelo qual você deve usá-lo.

Quanto ao que eles significam:

  • Melhor manutenção significa que é mais fácil mudar e não muda com tanta frequência. Como há menos código e esse código é focado em algo específico, se você precisar alterar algo que não está relacionado à classe, a classe não precisará ser alterada. Além disso, quando você precisar alterar a classe, desde que não precise alterar a interface pública, você só precisará se preocupar com essa classe e nada mais.
  • Teste fácil significa menos testes, menos configuração. Como não existem tantas partes móveis na classe, o número de possíveis falhas na classe é menor e, portanto, há menos casos que você precisa testar. Haverá menos campo / membros privados para configurar.
  • Por causa dos dois acima, você obtém uma classe que muda menos e falha menos e, portanto, é mais robusta.

Tente criar código por um tempo, seguindo o princípio e revisite-o posteriormente para fazer algumas alterações. Você verá a enorme vantagem que ele oferece.

Você faz o mesmo para cada classe e acaba com mais classes, todas em conformidade com o SRP. Isso torna a estrutura mais complexa do ponto de vista das conexões, mas a simplicidade de cada classe a justifica.


Não acho que os argumentos aqui apresentados sejam justificados. Em particular, como você evita jogos de soma zero em que a simplificação de uma classe faz com que outras se tornem mais complicadas, sem efeito líquido na base de código como um todo? Creio que a resposta de @ Doval aborda esse problema.

2
Você faz o mesmo para todas as classes. Você termina com mais aulas, todas em conformidade com o SRP. Isso torna a estrutura mais complexa do ponto de vista das conexões. Mas a simplicidade de cada classe justifica isso. Eu gosto da resposta de @ Doval, no entanto.
Miyamoto Akira

Eu acho que você deve adicionar isso à sua resposta.

4

Aqui estão os argumentos que, na minha opinião, apoiam a alegação de que o Princípio de Responsabilidade Única é uma boa prática. Também forneço links para mais literatura, onde você pode ler raciocínios ainda mais detalhados - e mais eloquentes que os meus:

  • Melhor manutenção : idealmente, sempre que uma funcionalidade do sistema precisar mudar, haverá uma e apenas uma classe que deverá ser alterada. Um mapeamento claro entre classes e responsabilidades significa que qualquer desenvolvedor envolvido no projeto pode identificar qual classe é essa. (Como notou @ MaciejChałapuk, consulte Robert C. Martin, "Código Limpo" .)

  • Teste mais fácil : idealmente, as classes devem ter uma interface pública tão mínima quanto possível, e os testes devem abordar apenas essa interface pública. Se você não pode testar com clareza, porque muitas partes da sua classe são privadas, isso é um sinal claro de que sua classe tem muitas responsabilidades e que você deve dividi-la em subclasses menores. Observe que isso se aplica também aos idiomas em que não há alunos 'públicos' ou 'particulares'; Ter uma pequena interface pública significa que é muito claro para o código do cliente quais partes da classe ela deve usar. (Consulte Kent Beck, "Desenvolvimento Orientado a Testes" para obter mais detalhes.)

  • Código robusto : seu código não falhará com mais ou menos frequência porque está bem escrito; Mas, como todo o código, seu objetivo final é não se comunicar com a máquina , mas com os desenvolvedores do companheiro (ver Kent Beck, "padrões de implementação" , Capítulo 1.) A base de código claro é mais fácil raciocinar sobre, por isso vai ser introduzido menos bugs , e menos tempo passará entre a descoberta e a correção de um bug.


Se algo tiver que mudar por motivos comerciais, acredite em mim com o SRP, você terá que modificar mais de uma classe. Dizer que, se uma mudança de classe é apenas por um motivo, não é o mesmo que, se houver uma mudança, isso afetará apenas uma classe.
Bastien Vandamme

@ B413: Eu não quis dizer que qualquer alteração implicará uma única alteração em uma única classe. Muitas partes do sistema podem precisar de alterações para cumprir uma única alteração comercial. Mas talvez você tenha razão e eu deveria ter escrito "sempre que uma funcionalidade do sistema precisar mudar".
logC

3

Existem várias razões, mas a que eu mais gosto é a abordagem usada por muitos dos primeiros programas UNIX: Faça uma coisa bem. Já é bastante difícil fazer isso com uma coisa, e aumentando a dificuldade, mais coisas você tenta fazer.

Outro motivo é limitar e controlar os efeitos colaterais. Eu amei minha combinação de máquina de café abridor de porta. Infelizmente, o café geralmente transbordava quando recebi visitas. Esqueci de fechar a porta depois que fiz café no outro dia e alguém a roubou.

Do ponto de vista psicológico, você pode acompanhar apenas algumas coisas de cada vez. As estimativas gerais são sete mais ou menos dois. Se uma classe faz várias coisas, você precisa acompanhar todas elas de uma vez. Isso reduz sua capacidade de rastrear o que você está fazendo. Se uma classe fizer três coisas e você desejar apenas uma delas, poderá esgotar sua capacidade de acompanhar as coisas antes de realmente fazer qualquer coisa com a classe.

Fazer várias coisas aumenta a complexidade do código. Além do código mais simples, aumentar a complexidade aumenta a probabilidade de erros. Desse ponto de vista, você deseja que as aulas sejam o mais simples possível.

Testar uma classe que faz uma coisa é muito mais simples. Você não precisa verificar se a segunda coisa que a classe fez ou não aconteceu em todos os testes. Você também não precisa corrigir as condições quebradas e testar novamente quando um desses testes falha.


2

Porque o software é orgânico. Os requisitos mudam constantemente para que você tenha que manipular os componentes com a menor dor de cabeça possível. Ao não seguir os princípios do SOLID, você pode acabar com uma base de código definida em concreto.

Imagine uma casa com uma parede de concreto com carga. O que acontece quando você remove esse muro sem nenhum apoio? A casa provavelmente entrará em colapso. No software, não queremos isso; portanto, estruturamos os aplicativos de maneira que você possa mover / substituir / modificar componentes facilmente sem causar muitos danos.


1

Eu sigo o pensamento: 1 Class = 1 Job.

Usando uma analogia de Fisiologia: Motor (Sistema Neural), Respiração (Pulmões), Digestivo (Estômago), Olfativo (Observação), etc. Cada um deles terá um subconjunto de controladores, mas cada um tem apenas uma responsabilidade, seja para gerenciar a maneira como cada um dos seus respectivos subsistemas funciona ou se é um subsistema de ponto final e executa apenas uma tarefa, como levantar um dedo ou cultivar um folículo piloso.

Não confunda o fato de que pode estar atuando como gerente, e não como trabalhador. Alguns trabalhadores acabam sendo promovidos a gerente, quando o trabalho que eles estavam executando se tornou muito complicado para um processo lidar sozinho.

A parte mais complicada do que experimentei é saber quando designar uma classe como processo de operador, supervisor ou gerente. Independentemente, você precisará observar e denotar sua funcionalidade para 1 responsabilidade (Operador, Supervisor ou Gerente).

Quando uma Classe / Objeto executa mais de uma dessas funções de tipo, você descobrirá que o processo geral começará a ter problemas de desempenho ou processar gargalos.


Mesmo que a implementação do processamento de oxigênio atmosférico em hemoglobina deva ser tratada como uma Lungclasse, eu diria que uma instância de Persondeveria ainda Breathee, portanto, a Personclasse deve conter lógica suficiente para, no mínimo, delegar as responsabilidades associadas a esse método. Eu sugeriria ainda que uma floresta de entidades interconectadas que são acessíveis apenas através de um proprietário comum geralmente é mais fácil de raciocinar do que uma floresta que possui vários pontos de acesso independentes.
Supercat

Sim, nesse caso, o Pulmão é Gerente, embora o Villi seja um Supervisor, e o processo de Respiração seria a classe Worker para transferir as partículas de Ar para a corrente sanguínea.
precisa saber é o seguinte

@GoldBishop: Talvez a visão correta seria dizer que uma Personclasse tem como função delegar toda a funcionalidade associada a uma entidade monstruosamente complicada do tipo "ser humano do mundo real", incluindo a associação de suas várias partes . Ter uma entidade fazendo 500 coisas é feio, mas se é assim que funciona o sistema do mundo real, ter uma classe que delega 500 funções enquanto mantém uma identidade pode ser melhor do que ter que fazer tudo com peças desunidas.
supercat

@supercat Ou você pode simplesmente compartimentar a coisa toda e ter 1 classe com o supervisor necessário nos subprocessos. Dessa forma, você poderia (em teoria) separar cada processo da classe base, Personmas ainda reportar a cadeia sobre sucesso / fracasso, mas não necessariamente afetando o outro. 500 funções (embora definidas como exemplo, seriam exageradas e não suportáveis), tento manter esse tipo de funcionalidade derivado e modular.
precisa saber é o seguinte

@GoldBishop: Eu gostaria que houvesse alguma maneira de declarar um tipo "efêmero", de modo que o código externo pudesse invocar membros ou passá-lo como parâmetro para os métodos do tipo, mas nada mais. De uma perspectiva de organização de código, aulas subdividindo faz sentido, e mesmo tendo código fora invocar métodos um nível baixo (por exemplo, Fred.vision.lookAt(George)faz sentido, mas permitindo que o código para dizer someEyes = Fred.vision; someTubes = Fred.digestionparece icky uma vez que obscurece a relação entre someEyese someTubes.
supercat

1

Especialmente com princípios tão importantes como a responsabilidade única, eu pessoalmente esperaria que haja muitas razões pelas quais as pessoas adotam esse princípio.

Alguns desses motivos podem ser:

  • Manutenção - O SRP garante que a mudança de responsabilidade em uma classe não afeta outras responsabilidades, simplificando a manutenção. Isso ocorre porque se cada classe tiver apenas uma responsabilidade, as alterações feitas em uma responsabilidade serão isoladas de outras responsabilidades.
  • Teste - Se uma classe tem uma responsabilidade, fica muito mais fácil descobrir como testar essa responsabilidade. Se uma classe tem múltiplas responsabilidades, você deve verificar se está testando a correta e se o teste não é afetado por outras responsabilidades da classe.

Observe também que o SRP vem com um conjunto de princípios do SOLID. Seguir o SRP e ignorar o resto é tão ruim quanto não fazê-lo em primeiro lugar. Portanto, você não deve avaliar o SRP por si só, mas no contexto de todos os princípios do SOLID.


... test is not affected by other responsibilities the class has, você poderia, por favor, elaborar este?
585 Mahdi

1

A melhor maneira de entender a importância desses princípios é ter a necessidade.

Quando eu era um programador iniciante, não pensava muito no design; na verdade, nem sabia que existiam padrões de design. À medida que meus programas cresciam, mudar uma coisa significava mudar muitas outras coisas. Era difícil rastrear bugs, o código era enorme e repetitivo. Não havia muita hierarquia de objetos, as coisas estavam por toda parte. Adicionar algo novo ou remover algo antigo traria erros nas outras partes do programa. Vai saber.

Em pequenos projetos, isso pode não importar, mas em grandes projetos as coisas podem ser muito pesadelos. Mais tarde, quando me deparei com os conceitos de padrões de design, eu disse a mim mesmo: "Ah, sim, fazer isso tornaria as coisas muito mais fáceis então".

Você realmente não consegue entender a importância dos padrões de design até que seja necessário. Eu respeito os padrões porque, por experiência, posso dizer que eles tornam a manutenção de código fácil e robusta.

No entanto, assim como você, ainda estou incerto sobre o "teste fácil", porque ainda não tive a necessidade de fazer testes de unidade.


Os padrões estão de alguma forma conectados ao SRP, mas o SRP não é necessário ao aplicar padrões de design. É possível usar padrões e ignorar completamente o SRP. Usamos padrões para atender a requisitos não funcionais, mas o uso de padrões não é necessário em nenhum programa. Os princípios são essenciais para tornar o desenvolvimento menos doloroso e (IMO) deve ser um hábito.
Maciej Chałapuk

Eu concordo completamente com você. O SRP, em certa medida, os executores limitam o design e a hierarquia dos objetos. É uma boa mentalidade para um desenvolvedor entrar, pois faz com que ele comece a pensar em termos de objetos e como devem ser. Pessoalmente, também teve um impacto muito bom no meu desenvolvimento.
harsimranb

1

A resposta é, como outros já apontaram, todos eles estão corretos, e todos se alimentam, mais fáceis de testar facilitam a manutenção tornam mais fácil o código, mais robusto facilita a manutenção e assim por diante ...

Tudo isso se resume a um código principal - o código deve ser tão pequeno e fazer o mínimo necessário para concluir o trabalho. Isso se aplica a um aplicativo, um arquivo ou uma classe, tanto quanto a uma função. Quanto mais coisas um código faz, mais difícil é entender, manter, estender ou testar.

Eu acho que pode ser resumido em uma palavra: escopo. Preste muita atenção ao escopo dos artefatos, quanto menor o escopo em qualquer ponto específico de um aplicativo, melhor.

Escopo expandido = mais complexidade = mais maneiras de as coisas darem errado.


1

Se uma classe é muito grande, fica difícil manter, testar e entender, outras respostas cobriram essa vontade.

É possível que uma classe tenha mais de uma responsabilidade sem problemas, mas você logo enfrenta problemas com classes muito complexas.

No entanto, ter uma regra simples de “apenas uma responsabilidade” facilita saber quando você precisa de uma nova classe .

Por mais que definir “responsabilidade” seja difícil, isso não significa “faça tudo o que a especificação do aplicativo diz”, a verdadeira habilidade é saber como dividir o problema em pequenas unidades de “responsabilidade”.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.