He hecho bastante javascript a lo largo de los años y estoy usando un enfoque más orientado a objetos, específicamente con el patrón del módulo. ¿Qué tipo de enfoque utiliza para evitar una base de código más grande para convertirse en espagueti? ¿Hay algún "mejor enfoque"?
8
Respuestas:
Dan Wahlin brinda orientación específica sobre cómo evitar el código de spaghetti en JavaScript.
Describe algunos patrones de diseño de JavaScript que dan estructura al código.
Prefiero el patrón prototipo revelador . Mi segundo favorito es el patrón de módulo revelador , que difiere ligeramente del patrón de módulo estándar en que puede declarar el ámbito público / privado.
fuente
steal()
) que le permite tener una aplicación muy bien estructurada mientras compila scripts en 1 o 2 archivos minificados.Si está utilizando OOP en un idioma moderno, el mayor peligro generalmente no es el " código de espagueti " sino el " código de ravioles". Puede terminar dividiendo y conquistando problemas hasta donde su base de código se compone de piezas muy pequeñas, funciones y objetos pequeños, todo vagamente acoplado y desempeñando responsabilidades singulares pero pequeñas, todo probado contra pruebas unitarias, con una telaraña de interacciones abstractas Esto hace que sea muy difícil razonar sobre lo que está sucediendo en términos de cosas como los efectos secundarios. Y es fácil pensar obstinadamente que usted diseñó esto maravillosamente, ya que las piezas individuales pueden ser hermosas y todas se adhieren a SOLID, mientras aún encuentran su cerebro al borde de explotar por la complejidad de todas las interacciones al tratar de comprender el sistema en su totalidad.
Y si bien es muy fácil razonar sobre lo que cualquiera de estos objetos o funciones hace individualmente, ya que desempeñan una responsabilidad tan singular y simple y tal vez incluso hermosa al expresar al menos sus dependencias abstractas a través de DI, el problema es que cuando se desea Para analizar el panorama general, es difícil imaginar qué mil cosas pequeñas con una telaraña de interacciones suman en última instancia. Por supuesto, la gente dice, solo mira los grandes objetos y las grandes funciones que están documentados y no profundizas en los más pequeños, y por supuesto, eso ayuda a comprender al menos lo que se supone que debe suceder de una manera de alto nivel. ..
Sin embargo, eso no ayuda mucho cuando realmente necesita cambiar o depurar el código, momento en el que debe ser capaz de descubrir qué suman todas estas cosas en cuanto a ideas de nivel inferior como efectos secundarios y cambios de estado persistentes y cómo mantener invariantes en todo el sistema. Y es bastante difícil reconstruir los efectos secundarios que ocurren entre las interacciones de miles de cosas pequeñas, ya sea que estén usando interfaces abstractas para comunicarse entre sí o no.
ECS
Entonces, lo último que encontré para mitigar este problema es en realidad los sistemas de componentes de entidad, pero eso podría ser excesivo para muchos proyectos. Me he enamorado de ECS hasta el punto de que ahora, incluso cuando escribo pequeños proyectos, uso mi motor ECS (aunque ese pequeño proyecto solo puede tener uno o dos sistemas). Sin embargo, para las personas que no están interesadas en ECS, he estado tratando de entender por qué ECS simplificó tanto la capacidad de comprender el sistema y creo que estoy en algunas cosas que deberían ser aplicables para muchos proyectos, incluso cuando no lo hacen. usar una arquitectura ECS.
Bucles homogéneos
Un comienzo básico es favorecer bucles más homogéneos que tienden a implicar más pases sobre los mismos datos, pero pases más uniformes. Por ejemplo, en lugar de hacer esto:
... de alguna manera parece ayudar mucho si haces esto en su lugar:
Y eso puede parecer un derroche en bucle sobre los mismos datos varias veces, pero ahora cada pasada es muy homogénea. Le permite pensar: "Muy bien, durante esta fase del sistema, no sucede nada con estos objetos, excepto la física. Si hay cosas que están cambiando y efectos secundarios, todos están cambiando de una manera muy uniforme. " Y de alguna manera encuentro que eso ayuda a razonar sobre la base de código tanto, mucho.
Si bien parece un desperdicio, también puede ayudarlo a encontrar más oportunidades para paralelizar el código cuando se aplican tareas uniformes sobre todo en cada ciclo. Y también tiende a alentarun mayor grado de desacoplamiento Solo por naturaleza, cuando tiene estos pases divorciados que no intentan hacer todo a un objeto en un solo pase, tiende a encontrar más oportunidades para desacoplar fácilmente el código y mantenerlo desacoplado. En ECS, los sistemas a menudo están completamente desacoplados entre sí y no existe una "clase" o "función" externa que los coordine manualmente. El ECS tampoco sufre fallos repetidos de caché necesariamente ya que no necesariamente recorre los mismos datos varias veces (cada ciclo puede acceder a diferentes componentes ubicados completamente en otra parte de la memoria, pero asociados a las mismas entidades). Los sistemas no tienen que coordinarse manualmente, ya que son autónomos y responsables del bucle. Solo necesitan acceso a los mismos datos centrales.
Entonces, esa es una manera de comenzar que puede ayudarlo a establecer un tipo de control más uniforme y simple sobre su sistema.
Aplanamiento de manejo de eventos
Otra es reducir la dependencia en el manejo de eventos. El manejo de eventos a menudo es necesario para descubrir cosas externas que ocurrieron sin sondeo, pero a menudo hay formas de evitar eventos de empuje en cascada que conducen a flujos de control y efectos secundarios muy difíciles de predecir. El manejo de eventos, por naturaleza, tiende a lidiar con cosas complejas que le suceden a un pequeño objeto a la vez, cuando queremos enfocarnos en cosas simples y uniformes que le suceden a muchos objetos a la vez.
Entonces, por ejemplo, en lugar de un evento de cambio de tamaño del sistema operativo, se cambia el tamaño de un control principal que luego comienza a cambiar los eventos de cambio de tamaño y pintura para cada niño, lo que podría generar más eventos en quién sabe dónde, solo puede activar eventos de cambio de tamaño y marcar el padre y los niños como
dirty
y necesita ser repintado. Incluso puede marcar todos los controles como necesarios para cambiar su tamaño, en ese momento seLayoutSystem
puede seleccionar eso y cambiar el tamaño de las cosas y activar eventos de cambio de tamaño para todos los controles relevantes.Luego, su sistema de renderizado de GUI podría despertarse con una variable de condición y recorrer los controles sucios y volver a pintarlos con un pase amplio (no una cola de eventos), y ese pase completo se centra en nada más que pintar una interfaz de usuario. Si hay una dependencia de orden jerárquico para volver a pintar, descubra las regiones sucias o rectángulos y vuelva a dibujar todo en esas regiones en el orden correcto de z para que no tenga que hacer un recorrido de árbol y simplemente pueda recorrer los datos en un muy moda simple y "plana", no una moda recursiva y "profunda".
Parece una diferencia tan sutil, pero por alguna razón, encuentro bastante útil desde el punto de vista del flujo de control. Realmente se trata de reducir la cantidad de cosas que suceden a los objetos individuales a la vez, tratando de apuntar a algo similar a SRP pero aplicado en términos de bucles y efectos secundarios: el " Principio de bucle de tarea única ", " El tipo de lado único Efecto por principio de bucle ".
Este tipo de flujo de control le permite pensar más en el sistema en términos de tareas grandes, pesadas pero extremadamente uniformes aplicadas en bucles, no todas las funciones y efectos secundarios que pueden ocurrir con un objeto individual a la vez. Por mucho que esto parezca que no haría una gran diferencia, descubrí que hizo toda la diferencia en el mundo, al menos en cuanto a la capacidad de mi propia mente para comprender el comportamiento de la base de código en todas las áreas que importaban al hacer cambios o depuración (que también encontré mucho menos necesidad de hacer con este enfoque).
Flujo de dependencias hacia los datos
Esta es probablemente la parte más controvertida de ECS, e incluso puede ser desastrosa para algunos dominios. Es una violación directa del Principio de Inversión de Dependencia de SOLID que establece que las dependencias deben fluir hacia abstracciones, incluso para módulos de bajo nivel. También viola la ocultación de información, pero para ECS al menos, no tanto como parece, ya que generalmente solo uno o dos sistemas accederán a los datos de cualquier componente.
Y creo que la idea de que las dependencias fluyan hacia las abstracciones funciona maravillosamente si sus abstracciones son estables (como en, inmutables). Las dependencias deben fluir hacia la estabilidad . Sin embargo, al menos en mi experiencia, las abstracciones a menudo no eran estables. Los desarrolladores nunca los entenderían bien y encontrarían la necesidad de cambiar o eliminar funciones (agregar no era tan malo), así como también depreciar algunas interfaces un año o dos después. Los clientes cambiarían de opinión de una manera que rompa los conceptos cuidadosos que los desarrolladores construyeron, derribando la fábrica abstracta para la casa abstracta de tarjetas abstractas.
Mientras tanto, encuentro que los datos son mucho más estables. Como ejemplo, ¿qué datos necesita un componente de movimiento en un juego? La respuesta es bastante simple. Necesita algún tipo de matriz de transformación 4x4 y necesita una referencia / puntero a un padre para permitir la creación de jerarquías de movimiento. Eso es. Esa decisión de diseño podría durar la vida útil de todo el software.
Puede haber algunas sutilezas como si deberíamos usar coma flotante de precisión simple o coma flotante de precisión doble para la matriz, pero ambas son decisiones decentes. Si se utiliza SPFP, la precisión es un desafío. Si se usa DPFP, entonces la velocidad es un desafío, pero ambas son buenas opciones que no necesitan ser cambiadas o necesariamente ocultas detrás de una interfaz. Cualquiera de las representaciones es con la que podemos comprometernos y mantenernos estables.
Sin embargo, ¿cuáles son todas las funciones necesarias para una
IMotion
interfaz abstracta y, lo que es más importante, cuáles son el conjunto mínimo ideal de funciones que debe proporcionar para hacer las cosas de manera efectiva contra las necesidades de todos los subsistemas que alguna vez lidiarán con el movimiento? Es mucho, mucho más difícil de responder sin comprender mucho más sobre la totalidad de las necesidades de diseño de la aplicación por adelantado. Y así, cuando tantas partes de la base de código terminan dependiendo de estoIMotion
, es posible que tengamos que reescribir tanto con cada iteración de diseño a menos que podamos hacerlo bien la primera vez.Por supuesto, en algunos casos la representación de datos podría ser muy inestable. Algo podría depender de una estructura de datos compleja que podría necesitar un reemplazo en el futuro debido a deficiencias en la estructura de datos, mientras que las necesidades funcionales del sistema asociado con la estructura de datos se anticipan fácilmente por adelantado. Por lo tanto, vale la pena ser pragmático y decidir las cosas caso por caso en cuanto a si las dependencias fluyen hacia abstracciones o datos, pero a veces al menos, los datos son más fáciles de estabilizar que las abstracciones, y no fue hasta que adopté ECS que incluso consideró hacer que las dependencias fluyan predominantemente hacia los datos (con efectos increíblemente simplificadores y estabilizadores).
Entonces, si bien esto puede parecer extraño, en los casos en que es mucho más fácil llegar a un diseño estable para los datos a través de una interfaz abstracta, en realidad sugiero dirigir las dependencias a datos antiguos simples. Esto podría ahorrarle muchas iteraciones repetidas de reescrituras. Sin embargo, en relación con los flujos de control y el código de espagueti y ravioles, esto también tenderá a simplificar sus flujos de control cuando no tenga que tener interacciones tan complejas antes de llegar finalmente a los datos relevantes.
fuente
Los estándares de código en general son útiles.
Eso significa:
Los módulos son definitivamente necesarios. También necesita coherencia en la forma en que se implementan las "clases", es decir, "métodos en el prototipo" vs "métodos en la instancia". También debe decidir a qué versión de ECMAScript apuntar, y si está apuntando, es decir, ECMAScript 5 utilice las características de lenguaje proporcionadas (por ejemplo, getters y setters).
Consulte también: TypeScript, que podría ayudarlo a estandarizar, por ejemplo, las clases. Un poco nuevo en este momento, pero no veo ninguna desventaja en usarlo ya que casi no hay bloqueo (porque se compila en JavaScript).
fuente
Una separación entre el código de trabajo y el código implementado es útil. Utilizo una herramienta para combinar y comprimir mis archivos javascript. Entonces puedo tener cualquier número de módulos en una carpeta, todos como archivos separados, para cuando estoy trabajando en ese módulo específico. Pero por tiempo de implementación, esos archivos se combinan en un archivo comprimido.
Uso Chirpy http://chirpy.codeplex.com/ , que también es compatible con SASS y coffeeScript.
fuente