Un grupo de amigos y yo hemos estado trabajando en un proyecto durante un tiempo, y queríamos inventar una buena manera de representar un escenario específico para nuestro producto. Básicamente, estamos trabajando en un juego de infierno de balas al estilo Touhou , y queríamos crear un sistema donde pudiéramos representar fácilmente cualquier posible comportamiento de bala que pudiéramos soñar.
Entonces eso es exactamente lo que hicimos; Hicimos una arquitectura realmente elegante que nos permitió separar el comportamiento de una viñeta en diferentes componentes que podrían adjuntarse a instancias de viñeta a voluntad, como el sistema de componentes de Unity . Funcionó bien, fue fácilmente extensible, flexible y cubrió todas nuestras bases, pero hubo un pequeño problema.
Nuestra aplicación también implica una gran cantidad de generación de procedimientos, es decir, generamos procesalmente los comportamientos de las viñetas. ¿Por qué es esto un problema? Bueno, nuestra solución OOP para representar el comportamiento de bala, aunque elegante, es un poco complicado de trabajar sin un humano. Los humanos son lo suficientemente inteligentes como para pensar en soluciones a problemas que sean lógicos e inteligentes. Los algoritmos de generación de procedimientos aún no son tan inteligentes, y nos ha resultado difícil implementar una IA que utilice nuestra arquitectura OOP a su máximo potencial. Es cierto que esa es una falla de la arquitectura es que no es intuitiva en todas las situaciones.
Entonces, para remediar este problema, básicamente introdujimos todos los comportamientos ofrecidos por los diferentes componentes en la clase de viñetas, de modo que todo lo que pudiéramos imaginar se ofrezca directamente en cada instancia de viñeta en lugar de en otras instancias de componentes asociadas. Esto hace que nuestros algoritmos de generación de procedimientos sean un poco más fáciles de trabajar, pero ahora nuestra clase de bala es un gran objeto divino . Es fácilmente la clase más grande en el programa hasta ahora con más de cinco veces más código que cualquier otra cosa. Es un poco difícil de mantener también.
¿Está bien que una de nuestras clases se haya convertido en un objeto divino, solo para que sea más fácil trabajar con otro problema? En general, ¿está bien tener olores de código en su código si admite una solución más fácil a un problema diferente?
fuente
Respuestas:
Cuando se crean programas del mundo real, a menudo existe una compensación entre mantenerse pragmático por un lado y mantenerse 100% limpio por el otro. Si mantenerse limpio le prohíbe enviar su producto a tiempo, entonces es mejor que use un poco de cinta adhesiva para sacar la mierda de la puerta.
Dicho esto, su descripción suena diferente: parece que no va a agregar un poco de cinta adhesiva para conductos, parece que va a arruinar toda su arquitectura porque no buscó lo suficiente para una mejor solución. Entonces, en lugar de buscar a alguien aquí en PSE que te dé una bendición sobre esto, es mejor que hagas una pregunta diferente donde describas algunos detalles de los problemas que tienes en profundidad, y mira si alguien te ofrece una idea que evite al dios -enfoque de clase.
Tal vez la clase de viñetas puede diseñarse para ser la fachada de muchas otras clases, por lo que la clase de viñetas se vuelve más pequeña. Tal vez el patrón de estrategia pueda ayudar para que la viñeta pueda delegar diferentes comportamientos a diferentes objetos de estrategia. Tal vez solo necesite un adaptador entre su componente de bala y su generador de procedimientos. Pero, sinceramente, sin conocer más detalles de su sistema, uno solo puede adivinar.
fuente
Interesante pregunta. Aunque estoy un poco parcial debido a mis experiencias anteriores, lo que me lleva a responder con No.
Respuesta corta: nunca dejamos de aprender. Cuando golpeas una pared así, es una oportunidad para mejorar tus habilidades arquitectónicas / de diseño, no una excusa para agregar olores de código.
La versión más larga es que me han hecho preguntas similares muchas veces en mis proyectos en el trabajo ahora, pero inevitablemente, terminamos discutiendo el tema con mucha más profundidad, luego el cuestionador original le dio crédito. Desea incluir mentalidades como el análisis de causa raíz con eso.
Los 5 Whys me parecieron particularmente útiles. Su explicación aquí se lee como la primera por qué:
¿Qué es exactamente lo que se simplifica? ¿Y por qué es así? Continúa por esa línea y lo que generalmente sucede es que comienzas a reconocer problemas mucho más fundamentales, pero al mismo tiempo, también te permiten soluciones más fundamentales.
En pocas palabras, después de pensar más profundamente sobre el problema en cuestión y, en particular, sus causas, en mi experiencia, la pregunta original terminó siendo nula y sin ningún interés para todos los participantes. Si tienes dudas, desafíalas y desaparecerán, o sabrás lo que tienes que hacer. Eso sí, en mi opinión, es una buena señal tener estas dudas sobre el código / diseño. Otros lo llaman instinto, pero para desafiar estos problemas, primero debes reconocerlos. Desde que hiciste ese primer paso, ¡felicidades! Ahora baja por la madriguera del conejo ...
fuente
Tener una clase de dios como esta nunca es deseable, ya que no solo significa que sus viñetas ahora son objetos monolíticos, sino que también sucede con su algoritmo de generación de procedimientos.
El primer paso habría sido analizar, ¿por qué exactamente su IA tuvo tantos problemas para lidiar con la complejidad de su patrón?
¿Por casualidad, trataste de convertir tu IA en un objeto de clase divina también, plenamente consciente de la semántica de cada propiedad posible? Si lo hiciste, ahí es donde se originó el problema.
Entonces, la solución no habría sido integrar todas las estrategias en la clase de bala en sí, sino descargar las sugerencias para la IA del núcleo de IA a las implementaciones de la estrategia.
Eso le habría dado la flexibilidad que originalmente deseaba, incluida la opción de extender el sistema mediante nuevas estrategias como lo desee sin el temor de tener efectos secundarios con comportamientos heredados.
En cambio, ahora tiene todos los problemas asociados con los objetos divinos: no solo su clase divina en sí misma es un monolito difícil de entender, difícil de depurar, sino que también ocurre con todos los demás componentes que acceden a dicha clase divina. Debido a la falta de abstracción, su IA ahora se convertirá en una abominación de complejidad similar, ya que tiene que ser consciente de todas las propiedades redundantes e individuales ahora.
Incluso ahora, ya está experimentando problemas con el mantenimiento. Estos problemas empeoran, especialmente en cuanto pierdes a los miembros del equipo que todavía tienen un modelo cognitivo de cómo se supone que debe funcionar esa clase.
Hasta ahora, cada proyecto que encontré que usaba tales clases de dios o funciones de dios fue reescrito completamente desde cero o cesó, sin excepciones.
fuente
Todo código malo desde el principio de los tiempos tiene una historia detrás de su evolución que lo hace parecer razonable paso a paso. La tuya no es la excepción. Los codificadores aprenden codificando. Hay aspectos de su problema que no podría haber previsto que parecen obvios ahora. Hay decisiones que tomó que fueron completamente razonables de manera incremental, pero que llevaron a su arquitectura en la dirección incorrecta en general. Necesita identificar y reexaminar esas decisiones.
Pregunte en su oficina si las personas tienen alguna idea sobre cómo solucionarlo. En la mayoría de los lugares en los que he trabajado, alrededor del 10-20% de los programadores tienen una habilidad real para ese tipo de cosas y han estado esperando la oportunidad. Averigua quiénes son esas personas. A menudo, son sus nuevos empleados quienes históricamente no han invertido en la arquitectura actual quienes pueden ver las alternativas más fácilmente. Combínalos con uno de tus veteranos y te sorprenderá lo que se les ocurre juntos.
fuente
En algunos casos esto es definitivamente aceptable. Sin embargo, me resulta difícil creer que no haya una buena solución utilizando tanto la generación de procedimientos como su agradable arquitectura de comportamiento basada en componentes / adjuntos. Si todos los comportamientos se introdujeron en la clase de viñetas, no hay diferencia funcional entre el objeto dios y la versión de arquitectura ordenada. ¿Qué dificultó el uso de sus algoritmos de generación de procedimientos?
Creo que una nueva pregunta (¿tal vez aquí, o en gamedev.stackexchange.com?), Donde se describen los problemas que tuvo con su arquitectura en combinación con proc. gen., sería realmente interesante. ¡Háganos saber aquí si también si hace una nueva pregunta!
fuente
Para inspirarte, quizás quieras echar un vistazo a la programación funcional, o más precisamente, a los tipos ajustados. La idea es que las cosas no funcionan son imposibles de representar en el sistema de tipos que tiene disponible. Por ejemplo, supongamos que tiene una pistola que tiene los componentes "disparar balas" y "sostener balas". Si son dos componentes separados, hay configuraciones que no tienen sentido: puede tener una pistola que dispara balas pero no tiene ninguna en su almacenamiento, o una pistola que almacena balas pero no las dispara.
En este nivel de complejidad, estás pensando "correcto, un humano descubrirá que esto es inútil y evitará las combinaciones imposibles". Una mejor manera podría ser hacer que sea absolutamente imposible hacer esto. Por ejemplo, el componente para "disparar balas" podría requerir una referencia al componente "retener balas" (o simplemente mantenerlo como parte de sí mismo, aunque eso tiene sus propios problemas).
Si bien sus ejemplos pueden ser mucho más complicados, la clave sigue siendo limitar las posibles combinaciones, agregando restricciones. En cierto modo, si es demasiado complicado para que la generación de procedimientos sea correcta, probablemente sea demasiado complicado de todos modos. Piensa en todas las formas en que te estás restringiendo al diseñar las armas: ¿te dices a ti mismo "bien, esta arma ya tiene un lanzallamas, no tiene sentido agregar un lanzagranadas"? Eso es algo que puedes incorporar en tu heurística. Es posible que desee utilizar un mecanismo de probabilidad que sea un poco más complicado que simplemente dar una posibilidad fija de tener cada componente; es posible que tenga componentes que tienden a excluirse entre sí, o componentes que tienden a funcionar bien juntos.
Y por último, considere si en realidad es un problema lo suficientemente grande. ¿Está arruinando la diversión del juego? ¿Las armas resultantes son inútiles o están sobrecargadas? ¿Cuánto cuesta? ¿Uno de cada 10 no tiene sentido? Los juegos como Borderlands (con efectos de armas generados por procedimientos) a menudo ignoran las combinaciones de efectos sin sentido: ¿cuál es el punto de tener una escopeta con un alcance de 16x? Y sin embargo, eso sucede muy a menudo en Borderlands. Solo se juega para reír, en lugar de ser considerado un fracaso de los mecanismos de generación :)
fuente