Vi la charla de Stuart Sierra " Thinking In Data " y tomé una de las ideas como principio de diseño en este juego que estoy haciendo. La diferencia es que él está trabajando en Clojure y yo estoy trabajando en JavaScript. Veo algunas diferencias importantes entre nuestros idiomas en eso:
- Clojure es una programación idiomáticamente funcional
- La mayoría del estado es inmutable.
Tomé la idea de la diapositiva "Todo es un mapa" (de 11 minutos, 6 segundos a> 29 minutos). Algunas cosas que dice son:
- Siempre que vea una función que tome 2-3 argumentos, puede hacer un caso para convertirla en un mapa y simplemente pasar un mapa. Hay muchas ventajas en eso:
- No tiene que preocuparse por el orden de los argumentos.
- No tiene que preocuparse por ninguna información adicional. Si hay claves adicionales, esa no es realmente nuestra preocupación. Simplemente fluyen, no interfieren.
- No tienes que definir un esquema
- A diferencia de pasar un objeto, no hay datos ocultos. Pero argumenta que el ocultamiento de datos puede causar problemas y está sobrevalorado:
- Actuación
- Facilidad de implementación
- Tan pronto como se comunique a través de la red o entre procesos, debe hacer que ambas partes acuerden la representación de datos de todos modos. Ese es un trabajo adicional que puede omitir si solo trabaja en datos.
Lo más relevante para mi pregunta. Esto es 29 minutos en: "Haga que sus funciones sean componibles". Aquí está el ejemplo de código que usa para explicar el concepto:
;; Bad (defn complex-process [] (let [a (get-component @global-state) b (subprocess-one a) c (subprocess-two a b) d (subprocess-three a b c)] (reset! global-state d))) ;; Good (defn complex-process [state] (-> state subprocess-one subprocess-two subprocess-three))
Entiendo que la mayoría de los programadores no están familiarizados con Clojure, por lo que volveré a escribir esto en un estilo imperativo:
;; Good def complex-process(State state) state = subprocess-one(state) state = subprocess-two(state) state = subprocess-three(state) return state
Aquí están las ventajas:
- Fácil de probar
- Fácil de ver esas funciones de forma aislada
- Es fácil comentar una línea de esto y ver cuál es el resultado al eliminar un solo paso
- Cada subproceso podría agregar más información al estado. Si el subproceso uno necesita comunicar algo al subproceso tres, es tan simple como agregar una clave / valor.
- No hay repeticiones para extraer los datos que necesita fuera del estado solo para que pueda guardarlos nuevamente. Simplemente pase todo el estado y deje que el subproceso asigne lo que necesita.
Ahora, volviendo a mi situación: tomé esta lección y la apliqué a mi juego. Es decir, casi todas mis funciones de alto nivel toman y devuelven un gameState
objeto. Este objeto contiene todos los datos del juego. EG: Una lista de badGuys, una lista de menús, el botín en el suelo, etc. Aquí hay un ejemplo de mi función de actualización:
update(gameState)
...
gameState = handleUnitCollision(gameState)
...
gameState = handleLoot(gameState)
...
De lo que estoy aquí para preguntar es, ¿he creado alguna abominación que pervierte una idea que solo es práctica en un lenguaje de programación funcional? JavaScript no es idiomáticamente funcional (aunque se puede escribir de esa manera) y es realmente difícil escribir estructuras de datos inmutables. Una cosa que me preocupa es que asume que cada uno de esos subprocesos es puro. ¿Por qué es necesario hacer esa suposición? Es raro que cualquiera de mis funciones sean puras (con eso quiero decir que a menudo modifican gameState
. No tengo otros efectos secundarios complicados aparte de eso). ¿Estas ideas se desmoronan si no tienes datos inmutables?
Me preocupa que algún día me despierte y me dé cuenta de que todo este diseño es una farsa y realmente he estado implementando el antipatrón Big Ball Of Mud .
Honestamente, he estado trabajando en este código durante meses y ha sido genial. Siento que estoy obteniendo todas las ventajas que él reclama. Mi código es muy fácil de razonar. Pero soy un equipo de un solo hombre, así que tengo la maldición del conocimiento.
Actualizar
He estado codificando más de 6 meses con este patrón. Por lo general, en este momento me olvido de lo que he hecho y ahí es donde "¿escribí esto de manera limpia?" entra en juego. Si no lo hubiera hecho, realmente lucharía. Hasta ahora, no estoy luchando en absoluto.
Entiendo cómo sería necesario otro par de ojos para validar su mantenibilidad. Todo lo que puedo decir es que me importa la mantenibilidad en primer lugar. Siempre soy el evangelista más ruidoso para el código limpio, sin importar dónde trabajo.
Quiero responder directamente a aquellos que ya tienen una mala experiencia personal con esta forma de codificación. Entonces no lo sabía, pero creo que realmente estamos hablando de dos formas diferentes de escribir código. La forma en que lo hice parece estar más estructurada de lo que otros han experimentado. Cuando alguien tiene una mala experiencia personal con "Todo es un mapa", hablan de lo difícil que es mantenerlo porque:
- Nunca se sabe la estructura del mapa que requiere la función
- Cualquier función puede mutar la entrada de formas que nunca esperarías. Debe buscar en toda la base del código para descubrir cómo una clave en particular ingresó al mapa o por qué desapareció.
Para aquellos con tal experiencia, tal vez la base del código fue: "Todo toma 1 de N tipos de mapas". El mío es, "Todo toma 1 de 1 tipo de mapa". Si conoce la estructura de ese tipo 1, conoce la estructura de todo. Por supuesto, esa estructura generalmente crece con el tiempo. Es por eso...
Hay un lugar para buscar la implementación de referencia (es decir, el esquema). Esta implementación de referencia es un código que usa el juego para que no se desactualice.
En cuanto al segundo punto, no agrego / elimino claves al mapa fuera de la implementación de referencia, solo muto lo que ya está allí. También tengo un gran conjunto de pruebas automatizadas.
Si esta arquitectura finalmente colapsa bajo su propio peso, agregaré una segunda actualización. De lo contrario, suponga que todo va bien :)