Princípio de responsabilidade única - estou usando demais?

13

Para referência - http://en.wikipedia.org/wiki/Single_responsibility_principle

Eu tenho um cenário de teste em que em um módulo do aplicativo é responsável pela criação de entradas do razão. Existem três tarefas básicas que podem ser realizadas -

  • Veja as entradas do razão existentes no formato de tabela.
  • Crie uma nova entrada no razão usando o botão criar.
  • Clique em uma entrada do razão na tabela (mencionada no primeiro ponteiro) e veja seus detalhes na próxima página. Você pode anular uma entrada do razão nesta página.

(Existem mais algumas operações / validações em cada página, mas, por uma questão de brevidade, vou limitá-la a estas)

Então eu decidi criar três classes diferentes -

  • LedgerLandingPage
  • CreateNewLedgerEntryPage
  • ViewLedgerEntryPage

Essas classes oferecem os serviços que poderiam ser realizados nessas páginas e os testes do Selenium usam essas classes para levar a aplicação a um estado em que eu poderia fazer certa afirmação.

Quando eu estava analisando o artigo com meu colega, ele ficou abatido e me pediu para fazer uma aula única para todos. Embora eu ainda sinta que meu design é muito limpo, duvido que esteja usando demais o princípio de responsabilidade única

Tarun
fonte
2
IMHO não faz sentido tentar responder à sua pergunta em geral, sem saber duas coisas: Qual o tamanho dessas classes (por contagem de métodos e LOC)? E quanto maior / mais complexo eles devem crescer no futuro próximo?
Péter Török
2
10 métodos cada um é IMHO uma indicação clara de não colocar o código em uma classe com 30 métodos.
Doc Brown
6
Este é um território limítrofe: uma classe de 100 LOC e 10 métodos não é muito pequena e um dos 300 LOC não é muito grande. No entanto, 30 métodos em uma única classe me parecem muito. No geral, costumo concordar com o @Doc, pois é menos risco ter muitas turmas pequenas do que ter uma turma com excesso de peso. Especialmente tendo em conta que as classes naturalmente tendem a ganhar peso ao longo do tempo ...
Péter Török
1
@ PéterTörök - Concordo, a menos que o código possa ser refatorado em uma classe que possa ser usada intuitivamente que reutilize o código em vez de duplicar a funcionalidade, como eu esperava que o OP tivesse.
SoylentGray
1
Talvez a aplicação do MVC esclareça tudo isso: três visualizações, um modelo (Ledger) e provavelmente três controladores.
kevin cline

Respostas:

19

Citando @YannisRizos aqui :

Essa é uma abordagem instintiva e provavelmente correta, pois o mais provável é mudar a lógica de negócios do gerenciamento de livros contábeis. Se isso acontecer, seria muito mais fácil manter uma classe do que três.

Discordo, pelo menos agora, após cerca de 30 anos de experiência em programação. Classes menores IMHO são melhores para manter em quase todos os casos em que consigo pensar em casos como este. Quanto mais funções você colocar em uma classe, menos estrutura terá e a qualidade do seu código será degradada - a cada dia um pouco. Quando entendi Tarun corretamente, cada uma dessas três operações

LedgerLandingPage

CreateNewLedgerEntryPage

ViewLedgerEntryPage

é um caso de uso, cada uma dessas classes consiste em mais de uma função para executar a tarefa relacionada e cada uma pode ser desenvolvida e testada separadamente. Assim, a criação de classes para cada um deles agrupa as coisas que pertencem uma à outra, facilitando a identificação da parte do código a ser alterada se algo mudar.

Se você possui funcionalidades comuns entre essas 3 classes, elas pertencem a uma classe base comum, à Ledgerprópria classe (presumo que você tenha uma, pelo menos, para as operações CRUD) ou a uma classe auxiliar separada.

Se você precisar de mais argumentos para criar muitas classes menores, recomendo dar uma olhada no livro de Robert Martin, "Clean Code" .

Doc Brown
fonte
essa é toda a razão pela qual criei três classes diff em vez de colocar tudo em uma classe. Como não há funcionalidade comum entre nenhuma delas, não tenho classe comum. Obrigado pelo link.
Tarun
2
"É melhor manter turmas menores em quase todos os casos em que consigo pensar." Acho que nem o Clean Code iria tão longe. Você realmente argumentaria que, em um padrão MVC, deveria haver um controlador por página (ou caso de uso, como você diz), por exemplo? Na refatoração, Fowler possui dois odores de código juntos: um referente a ter muitos motivos para alterar uma classe (SRP) e outro referente a ter que editar muitas classes para uma alteração.
Pd #
@pdr, o OP afirma que não há funcionalidade comum entre essas classes; portanto, para mim, é difícil imaginar uma situação em que você precise tocar em mais de uma para fazer uma única alteração.
Péter Török
@pdr: Se você tem muitas classes para editar para uma alteração, é principalmente um sinal de que você não refatorou a funcionalidade comum para um local separado. Mas no exemplo acima, isso provavelmente levaria a uma quarta classe, e não apenas a uma.
Doc Brown
2
@pdr: BTW, você realmente leu "Código Limpo"? Bob Martin vai muito longe ao dividir o código em unidades menores.
Doc Brown
11

Não há uma implementação definitiva do Princípio da Responsabilidade Única, ou qualquer outro princípio. Você deve decidir com base no que é mais provável que seja alterado.

Como o @Karpie escreve em uma resposta anterior :

Você só precisa de uma classe, e a única responsabilidade dessa classe é gerenciar livros contábeis.

Essa é uma abordagem instintiva e provavelmente correta, pois o mais provável é mudar a lógica de negócios do gerenciamento de livros contábeis. Se isso acontecer, seria muito mais fácil manter uma classe do que três.

Mas isso deve ser examinado e decidido por caso. Você realmente não deve se preocupar com preocupações com minúsculas possibilidades de mudança e se concentrar na aplicação do princípio sobre o que é mais provável de mudar. Se a lógica do gerenciamento de livros contábeis for um processo multicamada e as camadas tendem a mudar independentemente ou são preocupações que devem ser separadas em princípio (lógica e apresentação), você deve usar classes separadas. É um jogo de probabilidade.

Uma pergunta semelhante sobre o uso excessivo dos princípios de design, que acho interessante: Padrões de design: quando usar e quando parar de fazer tudo usando padrões

yannis
fonte
1
SRP, na minha opinião, não é realmente um padrão de design.
Doc Brown
@DocBrown Claro que não um padrão de design, mas a discussão sobre a questão é extremamente relevante ... Boa prendedor embora, eu atualizei a resposta
yannis
4

Vamos examinar estas classes:

  • LedgerLandingPage: O papel desta classe é mostrar uma lista de ledgers. À medida que o aplicativo cresce, você provavelmente desejará adicionar métodos de classificação, filtragem, destaque e fusão transparente de dados de outras fontes de dados.

  • ViewLedgerEntryPage: Esta página mostra o razão em detalhes. Parece bem direto. Contanto que os dados sejam diretos. Você pode anular o razão aqui. Você também pode querer corrigi-lo. Ou comente ou anexe um arquivo, recibo ou número de reserva externo ou qualquer outra coisa. E depois de permitir a edição, você definitivamente deseja mostrar um histórico.

  • CreateLedgerEntryPage: se a ViewLedgerEntryPagecapacidade de edição estiver completa, a responsabilidade dessa classe poderá ser cumprida editando uma "entrada em branco", que pode ou não fazer sentido.

Esses exemplos podem parecer um pouco exagerados, mas o ponto é que, de um UX, estamos falando de recursos aqui. Um único recurso é definitivamente uma responsabilidade distinta e única. Como os requisitos para esse recurso podem mudar e os requisitos de dois recursos diferentes podem mudar para duas direções opostas, e você deseja ter aderido ao SRP desde o início.
Por outro lado, você sempre pode colocar uma fachada em três classes, se ter uma única interface for mais conveniente por qualquer motivo que você puder imaginar.


Existe o uso excessivo de SRP : se você precisar de uma dúzia de classes para ler o conteúdo de um arquivo, é bastante seguro assumir que você está fazendo exatamente isso.

Se você olhar para o SRP do outro lado, na verdade, diz que uma única responsabilidade deve ser assumida por uma única classe. Observe, no entanto, que as responsabilidades existem apenas dentro dos níveis de abstração. Portanto, faz sentido que uma classe de um nível de abstração mais alto cumpra sua responsabilidade delegando o trabalho ao componente de nível inferior, o que é melhor alcançado através da inversão de dependência .

back2dos
fonte
Acho que nem eu poderia ter descrito a responsabilidade dessas classes melhor do que você.
Tarun
2

Essas classes têm apenas um motivo para mudar?

Se você ainda não o conhece, pode ter entrado na armadilha especulativa de design / sobre engenharia (muitas classes, padrões de design muito complexos aplicados). Você não satisfez YAGNI . Nos estágios iniciais do ciclo de vida do software (alguns), os requisitos podem não estar claros. Portanto, a direção da mudança atualmente não é visível para você. Se você perceber isso, mantenha-o em uma ou em uma quantidade mínima de aulas. No entanto, isso traz uma responsabilidade: assim que os requisitos e a direção da mudança forem claros, é necessário repensar o design atual e fazer uma refatoração para satisfazer o motivo da mudança.

Se você já sabe que existem três razões diferentes para a mudança e que elas são mapeadas para essas três classes, basta aplicar a dose certa de SRP. Novamente, isso traz alguma responsabilidade: se você estiver errado ou os requisitos futuros mudarem inesperadamente, será necessário repensar o design atual e fazer uma refatoração para satisfazer o motivo da mudança.

O ponto inteiro é:

  • Fique de olho nos drivers para mudanças.
  • Escolha um design flexível, que pode ser refatorado.
  • Esteja pronto para refatorar o código.

Se você tiver essa mentalidade, modelará o código sem muito medo de design especulativo / sobre engenharia.

No entanto, sempre há o fator tempo: para muitas alterações, você não terá o tempo necessário. A refatoração custa tempo e esforço. Muitas vezes encontrei situações em que sabia que precisava alterar o design, mas devido a restrições de tempo, tive que adiá-lo. Isso vai acontecer e não pode ser evitado. Descobri que é mais fácil refatorar sob código de engenharia do que sobre código de engenharia. O YAGNI me fornece uma boa orientação: em caso de dúvida, mantenha a quantidade de classes e a aplicação de padrões de design no mínimo.

Theo Lenndorff
fonte
1

Há três motivos para chamar o SRP como um motivo para dividir uma classe:

  • para que futuras alterações nos requisitos possam deixar algumas classes inalteradas
  • para que um bug possa ser isolado mais rapidamente
  • para diminuir a classe

Há três razões para se recusar a dividir uma classe:

  • para que futuras alterações nos requisitos não façam a mesma alteração em várias classes
  • para que a descoberta de bugs não envolva o rastreamento de longos caminhos de indireção
  • resistir às técnicas de quebra de encapsulamento (propriedades, amigos, o que for) que expõem informações ou métodos a todos quando são necessários apenas por uma classe relacionada

Quando olho para o seu caso e imagino alterações, algumas delas causarão alterações em várias classes (por exemplo, adicione um campo ao razão, você precisará alterar as três), mas muitas não o farão - por exemplo, adicionando classificação à lista de registros ou alterando as regras de validação ao adicionar uma nova entrada do razão. A reação do seu colega é provavelmente porque é difícil saber onde procurar um bug. Se algo parece errado na lista de livros, é porque foi adicionado incorretamente ou há algo errado com a lista? Se houver uma discrepância entre o resumo e os detalhes, o que está errado?

Também é possível que seu colega pense que as alterações nos requisitos, o que significa que você precisará alterar todas as três classes, são muito mais prováveis ​​do que as alterações nas quais você mudaria apenas uma. Essa poderia ser uma conversa realmente útil para se ter.

Kate Gregory
fonte
bom ver uma perspectiva diferente
Tarun
0

Eu acho que se o ledger fosse uma tabela em db, sugiro colocar essas tarefas thress em uma classe (por exemplo, DAO). Se o ledger tiver mais lógica e não for uma tabela em db, sugiro que devemos criar mais classe para executar essas tarefas (pode ser de duas ou três classes) e forneça uma classe de fachada simplesmente para o cliente.

Mark xie
fonte
bem, o razão é um recurso na interface do usuário e existem muitas operações relacionadas a ele que podem ser realizadas a partir de páginas diferentes.
Tarun
0

IMHO você não deveria estar usando aulas. Não há abstrações de dados aqui. Os ledgers são um tipo de dados concreto. Os métodos para manipulá-los e exibi-los são funções e procedimentos. Certamente haverá muitas funções comumente usadas para serem compartilhadas, como formatar uma data. Há pouco espaço para abstrair e ocultar dados em uma classe nas rotinas de exibição e seleção.

Há algum caso para ocultar o estado em uma classe para edição do razão.

Quer você esteja abusando ou não do SRP, você está abusando de algo mais fundamental: você está usando demais a abstração. É um problema típico, gerado pela noção mítica OO é um paradigma de desenvolvimento são.

A programação é 90% concreta: o uso da abstração de dados deve ser raro e cuidadosamente projetado. A abstração funcional, por outro lado, deve ser mais comum, pois os detalhes da linguagem e a escolha dos algoritmos precisam ser separados da semântica da operação.

Yttrill
fonte
-1

Você só precisa de uma classe, e a única responsabilidade dessa classe é gerenciar registros .

sevenseacat
fonte
2
Para mim, uma aula de '... gerente' geralmente mostra que você não se incomodava em dividi-la ainda mais. O que a classe está gerenciando? A apresentação, a persistência?
sebastiangeiger
-1. Colocar 3 ou mais casos de uso em 1 classe é exatamente o que o SRP está tentando evitar - e por boas razões.
Doc Brown
@sebastiangeiger Então, o que as três classes do OP estão fazendo? Apresentação ou persistência?
sevenseacat
@ Karpie Estou sinceramente esperando apresentação. Concedido, o exemplo não é realmente bom, mas eu esperaria que '... Página' em um site esteja fazendo algo semelhante a uma exibição.
sebastiangeiger
2
Na verdade, você só precisa de uma classe para toda a aplicação, com a única responsabilidade de fazer os usuários felizes ;-)
Péter Török