Tudo bem ter odores de código se ele admitir uma solução mais fácil para outro problema? [fechadas]


31

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?


1
Bem ... isso é meio difícil de responder. Na minha opinião NÃO. Especialmente se você trabalha em conjunto com outras pessoas. Se você trabalha sozinho, pode fazer o que quiser. Mas, em geral, depende de quem você trabalha.
Batata sarcástica

13
"Se é estúpido, e funciona; não é estúpido." Dito isto, você deve considerar que, mesmo que funcione exatamente como você deseja, se você não está impossibilitando a codificação futura dessa classe divina por causa de como decidiu corrigi-la.
Flater

8
É melhor cheirar e saber que você fede, depois cheirar apenas as outras pessoas percebem. :)
Reactgular

1
Parece que você precisa de uma classe de adaptador que forneça uma interface não OO ao seu código de procedimento, para que você possa manter o restante do código limpo.
Nikie

1
Parece que você construiu um sistema maravilhoso e decidiu explodir tudo no inferno agora que não funciona como esperado em um recurso. É isso mesmo que você quer? Tenha orgulho do seu trabalho!
Mast

Respostas:


74

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.


6
Apenas para reafirmar esta resposta, eu ia escrever algo com as mesmas duas recomendações. Em relação ao último parágrafo, o que o OP descreve parece ser um cheiro de código, indicando que é necessário outro nível de indireção. Se essa é uma classe de fábrica melhor, fachadas ou até mesmo um DSL completo que a AI pode gerar, fica por conta do OP.
Daniel B

2
Boas sugestões sobre Fachada / Estratégia para dividir o código em partes menores. Embora sem saber mais detalhes, é difícil saber o que sugerir.
user949300

25

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:

  • "Nós temos essa classe divina agora" Por quê?
  • "Porque simplifica o algoritmo de geração processual"

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 ...


9

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.


Uma coisa que muitas vezes falta nas discussões sobre o quão grande ou complexa deve ser a classe é uma distinção entre "quanto o código que contém uma referência do tipo X pode fazer com ele" versus "muita lógica deve estar no arquivo de classe para" X ". Se um cliente provavelmente tiver coleções de objetos que suportam uma variedade de recursos (por exemplo, "fnorble") e, muitas vezes, desejar pedir a todos os membros de uma coleção que, por exemplo, "fnorble se possível, não faça nada", terá uma interface que combina muitos desses métodos, pode ser muito mais útil do que tentar dividir a interface.
Supercat

Dada essa interface, é possível ter código encapsular objetos com qualquer combinação de habilidades, sem precisar lidar com combinações diferentes separadamente. A segregação das interfaces tornaria necessário escrever muitas classes de proxy diferentes, uma vez que proxies para um tipo que suporta o método X não podiam ser usados ​​para quebrar objetos que não podem X e proxies que podem quebrar objetos que não podiam X não ' não é possível expor o método X, mesmo que esteja quebrando um objeto que possa suportá-lo.
Supercat

8

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.


2

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!


3
Você pode dar um exemplo para uma situação em que ter uma classe god seria aceitável e desejável, enquanto essa classe não é apenas uma compilação automatizada de fontes individuais?
Ext3h 13/04/2015

Com aceitável, eu estava me referindo a "Tudo bem ter cheiros de código se ele admitir uma solução mais fácil para outro problema?" As classes de Deus geralmente são facilmente solucionáveis ​​usando a composição. Mas sim, existem certos odores de código que definitivamente nem sempre valem 100% da eliminação. No final, é necessário algum pragmatismo para realizar o trabalho :). (Isso não significa que acho que você deve descartar as práticas recomendadas por capricho. Acredito que a maioria das casas de software seria melhor seguir as práticas recomendadas com mais rigor).
21715 Roy T.

0

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 :)

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.