Estou trabalhando em uma equipe em que o líder da equipe é um defensor virulento dos princípios de desenvolvimento do SOLID. No entanto, ele não tem muita experiência em obter software complexo fora da porta.
Temos uma situação em que ele aplicou o SRP ao que já era uma base de código bastante complexa, que agora se tornou muito fragmentada e difícil de entender e depurar.
Agora, temos um problema não apenas com a fragmentação do código, mas também com o encapsulamento, pois os métodos dentro de uma classe que podem ter sido privados ou protegidos foram julgados como representando um 'motivo para mudar' e foram extraídos para classes e interfaces públicas ou internas que não está de acordo com os objetivos de encapsulamento do aplicativo.
Como temos alguns construtores de classe que assumem mais de 20 parâmetros de interface, nosso registro e resolução de IoC estão se tornando um monstro por si só.
Quero saber se existe alguma abordagem de 'refatorar longe do SRP' que possamos usar para ajudar a corrigir alguns desses problemas. Li que não viola o SOLID se eu criar várias classes de granulação mais grossa e vazias que 'agrupam' várias classes estreitamente relacionadas para fornecer um ponto único de acesso à soma de sua funcionalidade (por exemplo, imitando uma implementação de classe excessivamente SRP).
Além disso, não consigo pensar em uma solução que nos permita continuar pragmaticamente com nossos esforços de desenvolvimento, mantendo todos felizes.
Alguma sugestão ?
fonte
ISomething
). IMHO, essas abordagens são muito mais fáceis de manipular do que a injeção de dependência e resultam em código mais legível.Respostas:
Se sua classe tem 20 parâmetros no construtor, não parece que sua equipe sabe o que é SRP. Se você tem uma classe que faz apenas uma coisa, como tem 20 dependências? É como ir em uma viagem de pesca e levar uma vara de pescar, caixa de equipamento, suprimentos para acolchoados, bola de boliche, nunchucks, lança-chamas, etc.
Dito isto, o SRP, como a maioria dos princípios existentes, pode ser aplicado em excesso. Se você criar uma nova classe para incrementar números inteiros, sim, isso pode ser uma responsabilidade única, mas vamos lá. Isso é ridículo. Tendemos a esquecer que coisas como os princípios do SOLID existem para um propósito. O SOLID é um meio para um fim, não um fim em si mesmo. O fim é a manutenção . Se você deseja obter essa granularidade com o Princípio de responsabilidade única, é um indicador de que o zelo pelo SOLID cegou a equipe ao objetivo do SOLID.
Então, acho que o que estou dizendo é ... O SRP não é seu problema. É um mal-entendido do SRP ou uma aplicação incrivelmente granular. Tente fazer com que sua equipe mantenha a coisa principal a principal. E o principal é a manutenção.
EDITAR
Faça com que as pessoas projetem módulos de uma maneira que incentive a facilidade de uso. Pense em cada classe como uma mini API. Pense primeiro em "Como eu gostaria de usar essa classe" e depois implemente-a. Não pense apenas "O que essa classe precisa fazer". O SRP tem uma grande tendência a tornar as aulas mais difíceis de usar, se você não pensar muito em usabilidade.
EDIT 2
Se você está procurando dicas sobre refatoração, pode começar a fazer o que sugeriu - criar classes de granulação mais grossa para agrupar várias outras. Verifique se a classe de granulação mais grossa ainda está aderindo ao SRP , mas em um nível superior. Então você tem duas alternativas:
Quando você terminar a refatoração (mas antes de se comprometer com o repositório), revise seu trabalho e pergunte a si mesmo se a refatoração foi realmente uma melhoria da capacidade de manutenção e facilidade de uso.
fonte
Customer
classe e ter um código mais sustentável. Veja exemplos aqui: codemonkeyism.com/...Acho que é na refatoração de Martin Fowler que li uma contra-regra para o SRP, definindo para onde está indo longe demais. Há uma segunda pergunta, tão importante quanto "toda classe tem apenas um motivo para mudar?" e isso é "toda mudança afeta apenas uma classe?"
Se a resposta para a primeira pergunta for, em todos os casos, "yes", mas a segunda questão for "nem mesmo perto", será necessário examinar novamente como você está implementando o SRP.
Por exemplo, se adicionar um campo a uma tabela significa que você precisa alterar um DTO e uma classe de validador e uma classe de persistência e um objeto de modelo de exibição e assim por diante, então você criou um problema. Talvez você deva repensar como implementou o SRP.
Talvez você tenha dito que adicionar um campo é o motivo para alterar o objeto Customer, mas alterar a camada de persistência (digamos, de um arquivo XML para um banco de dados) é outro motivo para alterar o objeto Customer. Então você decide criar um objeto CustomerPersistence também. Mas se você fizer isso de forma que adicionar um campo AINDA exija uma alteração no objeto CustomerPersisitence, qual era o objetivo? Você ainda tem um objeto com dois motivos para mudar - ele não é mais o Cliente.
No entanto, se você introduzir um ORM, é bem possível que as classes funcionem de forma que, se você adicionar um campo ao DTO, ele alterará automaticamente o SQL usado para ler esses dados. Então você tem um bom motivo para separar as duas preocupações.
Em resumo, eis o que eu costumo fazer: se houver um equilíbrio aproximado entre o número de vezes que digo "não, há mais de um motivo para mudar esse objeto" e o número de vezes que digo "não, essa alteração será afetam mais de um objeto ", acho que tenho o equilíbrio certo entre SRP e fragmentação. Mas se os dois ainda estão altos, começo a me perguntar se há uma maneira diferente de separar as preocupações.
fonte
Só porque um sistema é complexo, não significa que você precise complicá-lo . Se você tem uma classe que possui muitas dependências (ou Colaboradores) como esta:
... então ficou muito complicado e você realmente não está seguindo o SRP , está? Aposto que, se você anotasse o que
MyAwesomeClass
faz em um cartão CRC, ele não caberia em um cartão de índice ou você teria que escrever em letras minúsculas ilegíveis.O que você tem aqui é que vocês seguiram apenas o Princípio de Segregação de Interface e podem ter levado ao extremo, mas essa é outra história. Você pode argumentar que as dependências são objetos de domínio (o que acontece), no entanto, ter uma classe que lida com 20 objetos de domínio ao mesmo tempo está estendendo um pouco demais.
O TDD fornecerá um bom indicador de quanto uma classe faz. Sem rodeios; se um método de teste possui um código de configuração que leva uma eternidade para ser gravado (mesmo se você refatorar os testes),
MyAwesomeClass
provavelmente você tem muitas coisas a fazer.Então, como você resolve esse enigma? Você move as responsabilidades para outras classes. Existem algumas etapas que você pode executar em uma classe com esse problema:
Um exemplo abstrato sobre responsabilidades de refatoração
Vamos
C
ser uma classe que tem várias dependênciasD1
,D2
,D3
,D4
que você precisa para refatorar para usar menos. Quando identificamos quais métodosC
chamam as dependências, podemos fazer uma lista simples:D1
-performA(D2)
,performB()
D2
-performD(D1)
D3
-performE()
D4
-performF(D3)
Olhando para a lista, podemos ver isso
D1
eD2
estamos relacionados entre si, pois a classe precisa deles de alguma forma. Também podemos ver essasD4
necessidadesD3
. Portanto, temos dois agrupamentos:Group 1
-D1
<->D2
Group 2
-D4
->D3
Os agrupamentos são um indicador de que a classe agora tem duas responsabilidades.
Group 1
- Um para manipular a chamada de dois objetos que precisam um do outro. Talvez você possa deixar sua classeC
eliminar a necessidade de lidar com ambas as dependências e deixar um deles cuidar dessas chamadas. Nesse agrupamento, é óbvio queD1
poderia ter uma referência aD2
.Group 2
- A outra responsabilidade precisa de um objeto para chamar outro. Não consegueD4
lidarD3
com a classe? Provavelmente, podemos eliminarD3
da classeC
deixandoD4
fazer as chamadas.Não tome minha resposta como definida, pois o exemplo é muito abstrato e faz muitas suposições. Tenho certeza de que existem mais maneiras de refatorar isso, mas pelo menos as etapas podem ajudá-lo a obter algum tipo de processo para mover responsabilidades, em vez de dividir as classes.
Editar:
Entre os comentários, @Emmad Karem diz:
É verdade que os objetos DAO tendem a ter muitos parâmetros, que você precisa definir em seu construtor, e os parâmetros geralmente são tipos simples, como string. No entanto, no exemplo de uma
Customer
classe, você ainda pode agrupar suas propriedades dentro de outras classes para simplificar as coisas. Como ter umaAddress
turma com ruas e umaZipcode
turma que contenha o CEP e também lide com a lógica de negócios, como a validação de dados:Isso é discutido mais adiante na postagem do blog "Nunca, nunca, nunca use String em Java (ou pelo menos com frequência)" . Como alternativa ao uso de construtores ou métodos estáticos para facilitar a criação dos subobjetos, você pode usar um padrão de construtor de fluidos .
fonte
Concordo com todas as respostas sobre SRP e como isso pode ser levado longe demais. Na sua postagem, você mencionou que, devido à "refatoração excessiva" para aderir ao SRP, você encontrou o encapsulamento quebrado ou sendo modificado. A única coisa que funcionou para mim é sempre seguir o básico e fazer exatamente o necessário para atingir um fim.
Ao trabalhar com sistemas Legacy, o "entusiasmo" de consertar tudo para torná-lo melhor geralmente é bastante alto nos líderes de equipe, especialmente aqueles que são novos nessa função. SOLID, apenas não possui SRP - são apenas os S. Certifique-se de que, se estiver seguindo o SOLID, não esqueça também o OLID.
Estou trabalhando em um sistema Legacy agora e começamos a seguir um caminho semelhante no começo. O que funcionou para nós foi uma decisão coletiva da equipe de fazer o melhor dos dois mundos - SOLID e KISS (Keep It Simple Stupid). Discutimos coletivamente as principais mudanças na estrutura do código e aplicamos o bom senso na aplicação de vários princípios de desenvolvimento. Eles são ótimos como diretrizes, não "Leis do Desenvolvimento S / W". A equipe não é apenas sobre o líder da equipe - é sobre todos os desenvolvedores da equipe. O que sempre funcionou para mim é colocar todos em uma sala e criar um conjunto compartilhado de diretrizes que sua equipe inteira concorda em seguir.
Com relação a como corrigir sua situação atual, se você usa um VCS e não adicionou muitos recursos novos ao seu aplicativo, sempre pode voltar para uma versão de código que toda a equipe considera compreensível, legível e sustentável. Sim! Estou pedindo que você jogue fora o trabalho e comece do zero. É melhor do que tentar "consertar" algo que estava quebrado e movê-lo de volta para algo que já existia.
fonte
A resposta é a manutenção e a clareza do código acima de tudo. Para mim, isso significa escrever menos código , não mais. Menos abstrações, menos interfaces, menos opções, menos parâmetros.
Sempre que avalio uma reestruturação de código ou adiciono um novo recurso, penso em quanto clichê será necessário em comparação com a lógica real. Se a resposta for superior a 50%, provavelmente significa que estou pensando demais.
Além do SRP, existem muitos outros estilos de desenvolvimento. No seu caso, parece que YAGNI está definitivamente ausente.
fonte
Muitas das respostas aqui são realmente boas, mas focam-se no lado técnico desta questão. Vou simplesmente acrescentar que parece que as tentativas do desenvolvedor de seguir o SRP parecem violar o SRP.
Você pode ver o blog de Bob aqui sobre essa situação, mas ele argumenta que, se uma responsabilidade é manchada em várias classes, o SRP da responsabilidade é violado porque essas classes mudam em paralelo. Suspeito que seu desenvolvedor realmente goste do design no topo do blog de Bob e talvez fique um pouco desapontado ao vê-lo destruído. Em particular porque viola o "Princípio Comum de Fechamento" - as coisas que mudam juntas permanecem juntas.
Lembre-se de que o SRP se refere a "motivo da mudança" e não a "faça uma coisa", e que você não precisa se preocupar com esse motivo até que uma mudança realmente ocorra. O segundo cara paga pela abstração.
Agora existe o segundo problema - o "advogado virulento do desenvolvimento do SOLID". Certamente não parece que você tenha um ótimo relacionamento com esse desenvolvedor, portanto, qualquer tentativa de convencê-lo dos problemas na base de código é frustrada. Você precisará reparar o relacionamento para ter uma discussão real dos problemas. O que eu recomendaria é cerveja.
Não é sério - se você não bebe a cabeça em uma cafeteria. Saia do escritório e relaxe em algum lugar, onde você pode conversar sobre isso informalmente. Em vez de tentar ganhar uma discussão em uma reunião, o que você não quer, faça uma discussão em algum lugar divertido. Tente reconhecer que esse desenvolvedor, que está deixando você louco, é um humano realmente funcional que está tentando colocar o software "fora da porta" e não quer enviar porcaria. Como você provavelmente compartilha esse terreno comum, pode começar a discutir como melhorar o design enquanto ainda está em conformidade com o SRP.
Se você pode reconhecer que o SRP é uma coisa boa, que apenas interpreta os aspectos de maneira diferente, provavelmente pode começar a ter conversas produtivas.
fonte
Eu concordo com a sua decisão de líder de equipe [atualização = 2012.05.31] de que o SRP é geralmente um bom pensamento. Mas eu concordo totalmente com o comentário do @ Spoike -s que um construtor com 20 argumentos de interface é longe demais.
A introdução do SRP com IoC move complexetyety de uma "classe multi-responsável" para muitas classes srp e uma inicialização muito mais complicada para o benefício de
Receio que você não possa reduzir a desfragmentação de código sem sacrificar o srp.
Mas você pode "aliviar a dor" da inicialização de código implementando uma classe de açúcar sintática que oculta a complexidade da inicialização em um construtor.
fonte