Eu e um grupo de amigos estamos trabalhando em um projeto há algum tempo, e queríamos inventar uma maneira agradável de OOP de representar um cenário específico para o nosso produto. Basicamente, estamos trabalhando em um jogo de balas no estilo Touhou , e queríamos criar um sistema em que pudéssemos representar facilmente qualquer comportamento possível da bala que poderíamos imaginar.
Então foi exatamente isso que fizemos; fizemos uma arquitetura realmente elegante que nos permitiu separar o comportamento de um marcador em diferentes componentes que poderiam ser anexados a instâncias de marcador à vontade, como o sistema de componentes do Unity . Funcionou bem, era facilmente extensível, era flexível e cobria todas as nossas bases, mas havia um pequeno problema.
Nossa aplicação também envolve uma grande quantidade de geração processual, ou seja, geramos processualmente os comportamentos das balas. Por que isso é um problema? Bem, nossa solução OOP para representar o comportamento de uma bala, embora elegante, é um pouco complicada de se trabalhar sem um ser humano. Os seres humanos são inteligentes o suficiente para pensar em soluções para problemas que são lógicos e inteligentes. Os algoritmos de geração de procedimentos ainda não são inteligentes, e achamos difícil implementar uma IA que use nossa arquitetura OOP em todo o seu potencial. É certo que essa é uma falha da arquitetura: ela não é intuitiva em todas as situações.
Portanto, para solucionar esse problema, basicamente incluímos todos os comportamentos oferecidos por diferentes componentes na classe bullet, para que tudo o que possamos imaginar seja oferecido diretamente em cada instância do marcador, em oposição a outras instâncias do componente associadas. Isso torna um pouco mais fácil trabalhar com nossos algoritmos de geração de procedimentos, mas agora nossa classe bullet é um grande objeto divino . É facilmente a maior classe do programa até agora, com mais de cinco vezes mais código do que qualquer outra coisa. É um pouco difícil de manter também.
Tudo bem que uma de nossas classes se transformou em um objeto divino, apenas para facilitar o trabalho com outro problema? Em geral, não há problema em sentir o cheiro do código no código, se ele admitir uma solução mais fácil para um problema diferente?
fonte
Respostas:
Ao criar programas do mundo real, geralmente há uma troca entre permanecer pragmático por um lado e permanecer 100% limpo por outro. Se ficar limpo o proíbe de enviar seu produto a tempo, é melhor você usar um pouco de fita adesiva para tirar a merda da porta.
Dito isso, sua descrição parece diferente - parece que você não vai adicionar um pouco de fita adesiva, parece que você vai arruinar toda a sua arquitetura porque não parecia suficientemente longa e difícil para uma solução melhor. Então, em vez de procurar alguém aqui no PSE que abençoe você, é melhor fazer uma pergunta diferente, descrevendo alguns detalhes dos problemas que você tem em profundidade e ver se alguém lhe oferece uma idéia que evita o deus abordagem de classe.
Talvez a classe de marcadores possa ser projetada para ser fachada de várias outras classes, para que a classe de marcadores se torne menor. Talvez o padrão de estratégia possa ajudar para que o marcador possa delegar comportamentos diferentes para diferentes objetos de estratégia. Talvez você precise apenas de um adaptador entre o componente marcador e o gerador de procedimentos. Mas, honestamente, sem conhecer mais detalhes do seu sistema, só podemos adivinhar.
fonte
Pergunta interessante. Estou um pouco tendencioso, devido às minhas experiências anteriores, o que me leva a responder com Não.
Resposta curta: nunca paramos de aprender. Quando você atinge uma parede como essa, é uma chance de melhorar suas habilidades de arquitetura / design, não uma desculpa para adicionar odores de código.
A versão mais longa é que já fiz muitas perguntas semelhantes em meus projetos em andamento agora, mas inevitavelmente acabamos discutindo a questão com muito mais profundidade, e o questionador original deu crédito a ela. Você deseja incluir mentalidades como análise de causa raiz.
Eu achei os 5 porquês particularmente úteis. Sua explicação aqui é como a primeira, por que:
O que é exatamente isso é simplificado? E porque é isso? Continue seguindo essa linha e o que normalmente acontece é que você começa a reconhecer problemas muito mais fundamentais, mas, ao mesmo tempo, eles permitem soluções mais fundamentais também.
Para encurtar a história, depois de pensar mais profundamente sobre o problema em questão e em particular suas causas, na minha experiência a pergunta original acabou sendo nula e totalmente desinteressante para todos os participantes. Se você tiver dúvidas, desafie-as e elas desaparecerão, ou você saberá o que precisa fazer. Veja bem, é um bom sinal na minha opinião ter essas dúvidas sobre código / design. Outros chamam de um pressentimento, mas para desafiar esses problemas, você deve primeiro reconhecê-los. Desde que você deu o primeiro passo, parabéns! Agora desça a toca do coelho ...
fonte
Ter uma classe divina como essa nunca é desejável, pois não significa apenas que suas balas agora são objetos monolíticos, mas o mesmo vale para o algoritmo de geração de procedimentos também.
O primeiro passo seria analisar, por que exatamente sua IA teve tanto problema em lidar com a complexidade do seu padrão?
Você, por acaso, tentou transformar sua IA também em um objeto de classe divina, totalmente ciente da semântica de todas as propriedades possíveis? Se você fez, é aí que o problema se originou.
A solução não seria integrar todas as estratégias à própria classe de bala, mas sim transferir a dica para a IA do núcleo da IA para as próprias implementações da estratégia.
Isso daria a flexibilidade que você originalmente desejava, incluindo a opção de estender o sistema por novas estratégias, conforme desejar, sem o medo de enfrentar efeitos colaterais com comportamentos herdados.
Em vez disso, você tem agora todos os problemas associados aos objetos god: não apenas a sua classe god em si é um monólito difícil de entender, é difícil de depurar, mas o mesmo vale para todos os outros componentes que acessam essa classe god. Devido à falta de abstração, sua IA agora se tornará uma abominação de complexidade semelhante, pois precisa estar ciente de todas as propriedades individuais redundantes agora.
Mesmo agora, você já está tendo problemas com a manutenção. Esses problemas estão piorando, especialmente quando você perde os membros da equipe que ainda têm um modelo cognitivo de como essa classe deve funcionar.
Até agora, todos os projetos que encontrei que usavam essas classes ou funções divinas foram completamente reescritos do zero ou cessaram, sem exceções.
fonte
Todo código ruim desde o início dos tempos tem uma história por trás de sua evolução que faz com que pareça razoável passo a passo. O seu não é exceção. Os codificadores aprendem codificando. Existem aspectos do seu problema que você não poderia prever que parecem óbvios agora. Existem decisões que você tomou que foram totalmente razoáveis em termos incrementais, mas levaram sua arquitetura na direção errada em geral. Você precisa identificar e reexaminar essas decisões.
Pergunte em seu escritório se as pessoas têm alguma idéia de como corrigi-lo. Na maioria dos lugares em que trabalhei, cerca de 10 a 20% dos programadores têm um talento especial para esse tipo de coisa e apenas aguardam a chance. Descobrir quem são essas pessoas. Freqüentemente, são seus novos contratados que não investem historicamente na arquitetura atual que podem ver alternativas mais facilmente. Combine-os com um de seus veteranos e você poderá se surpreender com o que eles criam juntos.
fonte
Em alguns casos, isso é definitivamente aceitável. No entanto, acho difícil acreditar que não haja uma boa solução usando a geração de procedimentos e sua bela arquitetura de comportamento baseada em componentes / anexos. Se todos os comportamentos foram atraídos para a classe bullet, não há diferença funcional entre o objeto god e a versão pura da arquitetura. O que tornou tão difícil o uso dos algoritmos de geração de procedimentos?
Eu acho que uma nova pergunta (talvez aqui, ou em gamedev.stackexchange.com?), Onde você descreve quais problemas teve com sua arquitetura em combinação com proc. gen., seria realmente interessante. Informe-nos aqui também se você fizer uma nova pergunta!
fonte
Para inspiração, você pode dar uma olhada na programação funcional ou, mais precisamente, nos tipos ajustados. A ideia é que as coisas não funcionam são impossíveis de representar no sistema de tipos que você tem disponível. Por exemplo, digamos que você tenha uma arma com os componentes "disparar balas" e "segurar balas". Se eles são dois componentes separados, há configurações que não fazem sentido - você pode ter uma arma que dispara balas, mas não tem nenhuma em seu armazenamento, ou uma arma que armazena balas, mas não as dispara.
Nesse nível de complexidade, você está pensando "certo, um humano descobrirá que isso é inútil e evitará combinações impossíveis". Uma maneira melhor pode ser tornar totalmente impossível fazer isso. Por exemplo, o componente para "disparar marcadores" pode exigir uma referência ao componente "reter marcadores" (ou apenas retê-lo como parte de si mesmo, embora isso tenha seus próprios problemas).
Embora seus exemplos possam ser muito mais complicados, a chave ainda está limitando as combinações possíveis, adicionando restrições. De certa forma, se é muito complicado para a geração de procedimentos dar certo, provavelmente é muito complicado de qualquer maneira. Pense em todas as maneiras pelas quais você se restringe ao projetar as armas - você se diz "certo, essa arma já possui um lança-chamas, não há sentido em adicionar um lançador de granadas"? Isso é algo que você pode incorporar em suas heurísticas. Você pode usar um mecanismo de probabilidade um pouco mais complicado do que apenas dar uma chance fixa de ter cada componente - você pode ter componentes que tendem a se excluir ou componentes que tendem a funcionar bem juntos.
E por último, considere se é realmente um problema grande o suficiente. Está arruinando a diversão do jogo? O resultado são armas que são inúteis ou sobrecarregadas? Quantos? Um em cada 10 é absurdo? Jogos como Borderlands (com efeitos de armas gerados proceduralmente) geralmente ignoram combinações sem sentido - qual é o sentido de ter uma espingarda com uma mira de 16x? E, no entanto, isso acontece com muita frequência em Borderlands. É apenas jogado para rir, ao invés de ser considerado um fracasso dos mecanismos de geração :)
fonte