Portanto, provavelmente como muitos, muitas vezes me vejo enfrentando problemas de design nos quais, por exemplo, há algum padrão / abordagem de design que parece se encaixar intuitivamente no problema e tem os benefícios desejados. Muitas vezes, existem algumas ressalvas que dificultam a implementação do padrão / abordagem sem algum tipo de trabalho em torno do qual nega os benefícios do padrão / abordagem. Posso facilmente iterar através de muitos padrões / abordagens, porque previsivelmente quase todos eles têm algumas advertências muito significativas em situações do mundo real nas quais simplesmente não há uma solução fácil.
Exemplo:
Vou lhe dar um exemplo hipotético baseado vagamente em um exemplo real que encontrei recentemente. Digamos que eu queira usar a composição sobre a herança, porque as hierarquias de herança impediram a escalabilidade do código no passado. Eu posso refatorar o código, mas depois descobrir que há alguns contextos em que a superclasse / classe básica simplesmente precisa chamar a funcionalidade na subclasse, apesar das tentativas de evitá-lo.
A próxima melhor abordagem parece estar implementando um padrão de meio delegado / observador e meio de composição para que a superclasse possa delegar comportamento ou para que a subclasse possa observar eventos da superclasse. Em seguida, a classe é menos escalável e sustentável, porque não está claro como deve ser estendido; também é complicado estender os ouvintes / delegados existentes. Além disso, as informações não ficam ocultas porque é necessário conhecer a implementação para ver como estender a superclasse (a menos que você use comentários muito extensivamente).
Então, depois disso, posso optar por simplesmente usar completamente os observadores ou delegados para evitar as desvantagens que surgem com a mistura pesada das abordagens. No entanto, isso vem com seus próprios problemas. Por exemplo, eu acho que acabo precisando de observadores ou delegados para uma quantidade crescente de comportamentos até acabar precisando de observadores / delegados para praticamente todos os comportamentos. Uma opção pode ser ter apenas um grande ouvinte / delegado para todo o comportamento, mas a classe de implementação acaba com muitos métodos vazios, etc.
Então eu poderia tentar outra abordagem, mas há tantos problemas com isso. Em seguida, o próximo e o outro, etc.
Esse processo iterativo fica muito difícil quando cada abordagem parece ter tantos problemas quanto qualquer outro e leva a uma espécie de paralisia da decisão de design . Também é difícil aceitar que o código acabe igualmente problemático, independentemente de qual padrão ou abordagem de design seja usada. Se eu acabar nessa situação, isso significa que o problema em si precisa ser repensado? O que os outros fazem quando se deparam com essa situação?
Edit: Parece haver uma série de interpretações da pergunta que eu quero esclarecer:
- Tirei OOP da questão completamente porque, na verdade, ele não é específico para OOP, além de ser muito fácil interpretar erroneamente alguns dos comentários que fiz ao passar sobre OOP.
- Alguns afirmaram que eu deveria adotar uma abordagem iterativa e tentar padrões diferentes, ou que deveria descartar um padrão quando ele parar de funcionar. Este é o processo que pretendi referir em primeiro lugar. Eu pensei que isso estava claro no exemplo, mas eu poderia ter deixado mais claro, então editei a pergunta para fazer isso.
Respostas:
Quando chego a uma decisão difícil como essa, geralmente me faço três perguntas:
Quais são os prós e os contras de todas as soluções disponíveis?
Existe uma solução que ainda não considerei?
Mais importante:
quais são exatamente os meus requisitos? Não são os requisitos em papel, os reais fundamentais?
Posso de alguma forma reformular o problema / ajustar seus requisitos, para que ele permita uma solução simples e direta?
Posso representar meus dados de uma maneira diferente, para permitir uma solução tão simples e direta?
Essas são as perguntas sobre os fundamentos do problema percebido. Pode acontecer que você estivesse realmente tentando resolver o problema errado. Seu problema pode ter requisitos específicos que permitem uma solução muito mais simples do que o caso geral. Questione sua formulação do seu problema!
Acho muito importante pensar nas três perguntas antes de continuar. Faça uma caminhada, passeie pelo escritório, faça o que for preciso para realmente refletir sobre as respostas a essas perguntas. No entanto, responder a essas perguntas não deve demorar muito tempo. Os horários adequados podem variar de 15 minutos a algo como uma semana, eles dependem da maldade das soluções que você já encontrou e de seu impacto sobre o todo.
O valor dessa abordagem é que você às vezes encontrará soluções surpreendentemente boas. Soluções elegantes. As soluções valem o tempo que você investiu em responder a essas três perguntas. E você não encontrará essa solução, se você digitar a próxima iteração imediatamente.
Obviamente, às vezes parece não existir uma boa solução. Nesse caso, você está preso às suas respostas à primeira pergunta, e o bom é simplesmente o menos ruim. Nesse caso, o valor de reservar um tempo para responder a essas perguntas é que você possivelmente evita iterações que provavelmente falharão. Apenas certifique-se de voltar à codificação dentro de um período de tempo adequado.
fonte
Primeiras coisas primeiro - os padrões são abstrações úteis, e não o fim, tudo é todo design, muito menos design OO.
Em segundo lugar - o OO moderno é inteligente o suficiente para saber que nem tudo é um objeto. Às vezes, o uso de funções simples e antigas, ou mesmo alguns scripts de estilo imperativo, produz uma solução melhor para determinados problemas.
Agora, para a carne das coisas:
Por quê? Quando você tem várias opções semelhantes, sua decisão deve ser mais fácil! Você não perderá muito escolhendo a opção "errada". E realmente, o código não é fixo. Tente algo, veja se é bom. Iterar .
Nozes duras. Problemas difíceis - problemas reais matematicamente difíceis são difíceis. Provavelmente difícil. Não há literalmente uma boa solução para eles. E acontece que os problemas fáceis não são realmente valiosos.
Mas tenha cuidado. Muitas vezes, tenho visto pessoas frustradas por não ter boas opções porque ficam presas ao problema de uma certa maneira ou cortam suas responsabilidades de uma maneira que não é natural para o problema em questão. "Nenhuma boa opção" pode ser um cheiro de que há algo fundamentalmente errado com sua abordagem.
Perfeito é o inimigo do bem. Faça algo funcionar e depois refatorá-lo.
Como mencionei, o desenvolvimento iterativo geralmente minimiza esse problema. Depois de conseguir algo funcionando, você está mais familiarizado com o problema, colocando-o em uma posição melhor para resolvê-lo. Você tem um código real para examinar e avaliar, não um design abstrato que não parece certo.
fonte
A situação que você está descrevendo parece uma abordagem de baixo para cima. Você pega uma folha, tenta consertá-la e descobre que está conectada a um galho que também está conectado a outro galho etc.
É como tentar construir um carro começando com um pneu.
O que você precisa é dar um passo atrás e olhar para a foto maior. Como essa folha se encaixa no design geral? Ainda está atual e correto?
Como seria o módulo se você o projetasse e implementasse do zero? A que distância desse "ideal" está sua implementação atual.
Dessa forma, você tem uma imagem maior do que trabalhar. (Ou, se você decidir que é muito trabalhoso, quais são os problemas).
fonte
Seu exemplo descreve uma situação que geralmente ocorre com partes maiores do código legado e quando você está tentando fazer uma refatoração "muito grande".
O melhor conselho que posso dar para essa situação é:
não tente alcançar seu objetivo principal em um "big bang" ,
aprenda a melhorar seu código em etapas menores!
Claro, isso é mais fácil de escrever do que fazer, então como fazer isso na realidade? Bem, você precisa de prática e experiência, isso depende muito do caso, e não existe uma regra rígida que diga "faça isso ou aquilo" que seja adequado para todos os casos. Mas deixe-me usar seu exemplo hipotético. "composição sobre herança" não é um objetivo de design de tudo ou nada, é um candidato perfeito para ser alcançado em várias pequenas etapas.
Digamos que você tenha notado que "composição por herança" é a ferramenta certa para o caso. Deve haver algumas indicações para que este seja um objetivo sensato, caso contrário você não o teria escolhido. Então, vamos supor que há muita funcionalidade na superclasse que é apenas "chamada" das subclasses, portanto essa funcionalidade é candidata a não permanecer nessa superclasse.
Se você notar que não pode remover imediatamente a superclasse das subclasses, pode começar primeiro refatorando a superclasse em componentes menores, encapsulando a funcionalidade mencionada acima. Comece com os frutos mais baixos, extraia primeiro alguns componentes mais simples, o que já tornará sua superclasse menos complexa. Quanto menor a superclasse, mais fáceis serão as refatorações adicionais. Use esses componentes das subclasses e da superclasse.
Se você tiver sorte, o código restante na superclasse se tornará tão simples ao longo deste processo que você poderá remover a superclasse das subclasses sem mais problemas. Ou, você notará que manter a superclasse não é mais um problema, já que você já extraiu o suficiente do código em componentes que deseja reutilizar sem herança.
Se você não sabe por onde começar, porque não sabe se uma refatoração será simples, às vezes a melhor abordagem é fazer uma refatoração de arranhões .
Obviamente, sua situação real pode ser mais complicada. Portanto, aprenda, ganhe experiência e seja paciente, pois isso leva anos. Existem dois livros que posso recomendar aqui, talvez você os ache úteis:
Refatoração por Fowler: descreve um catálogo completo de refatorações muito pequenas .
Trabalhando efetivamente com o código legado da Feathers: fornece excelentes conselhos sobre como lidar com grandes pedaços de código mal projetado e torná-lo mais testável em etapas menores
fonte
Às vezes, os dois melhores princípios de design são o KISS * e o YAGNI **. Não sinta a necessidade de agrupar todos os padrões de design conhecidos em um programa que apenas imprima "Olá, mundo!".
Edite após a atualização da pergunta (e espelhando o que Pieter B diz até certo ponto):
Às vezes, você toma uma decisão arquitetural desde o início, o que leva a um design específico que leva a todo tipo de feiura quando você tenta implementá-lo. Infelizmente, nesse ponto, a solução "adequada" é dar um passo atrás e descobrir como você chegou a essa posição. Se você ainda não consegue ver a resposta, continue recuando até conseguir.
Mas se o trabalho para fazer isso for desproporcional, é preciso uma decisão pragmática para apresentar a solução menos feia para o problema.
fonte
Quando estou nessa situação, a primeira coisa que faço é parar. Eu mudo para outro problema e trabalho nisso por um tempo. Talvez uma hora, talvez um dia, talvez mais. Isso nem sempre é uma opção, mas meu subconsciente trabalha com as coisas enquanto meu cérebro consciente faz algo mais produtivo. Eventualmente, volto a ele com novos olhos e tento novamente.
Outra coisa que faço é perguntar a alguém mais inteligente que eu. Isso pode assumir a forma de perguntar no Stack Exchange, ler um artigo na Web sobre o tópico ou perguntar a um colega que tem mais experiência nessa área. Muitas vezes, o método que eu acho que é a abordagem correta acaba sendo completamente errado para o que estou tentando fazer. Eu descaracterizei algum aspecto do problema e ele realmente não se encaixa no padrão que eu acho. Quando isso acontece, alguém mais diz: "Sabe, isso parece mais ..." pode ser uma grande ajuda.
Relacionado ao acima, há design ou depuração por confissão. Você vai a um colega e diz: "Vou lhe contar o problema que estou tendo e depois vou explicar as soluções que tenho. Você aponta problemas em cada abordagem e sugere outras abordagens. . " Muitas vezes, antes que a outra pessoa fale, como estou explicando, começo a perceber que um caminho que parecia igual aos outros é realmente muito melhor ou pior do que eu pensava inicialmente. A conversa resultante pode reforçar essa percepção ou apontar coisas novas em que não pensei.
Então TL; DR: Faça uma pausa, não force, peça ajuda.
fonte