He pasado alrededor de 20-40 horas desarrollando parte de un juego usando JavaScript y HTML5 Canvas. Cuando comencé no tenía idea de lo que estaba haciendo. Así que comenzó como una prueba de concepto y ahora se está desarrollando muy bien, pero no tiene pruebas automatizadas. El juego comienza a ser lo suficientemente complejo como para beneficiarse de algunas pruebas automáticas, pero parece difícil de hacer porque el código depende de un estado global mutante.
Me gustaría refactorizar todo usando Underscore.js , una biblioteca de programación funcional para JavaScript. Una parte de mí piensa que debería comenzar desde cero usando un estilo de programación funcional y pruebas. Pero, creo que refactorizar el código imperativo en código declarativo podría ser una mejor experiencia de aprendizaje y una forma más segura de llegar a mi estado actual de funcionalidad.
El problema es que sé cómo quiero que se vea mi código al final, pero no sé cómo convertir mi código actual en él. Espero que algunas personas aquí puedan darme algunos consejos al estilo del libro Refactoring y Working Effectively With Legacy Code.
Por ejemplo, como primer paso estoy pensando en "prohibir" el estado global. Tome todas las funciones que usan una variable global y páselo como parámetro.
El siguiente paso puede ser "prohibir" la mutación y siempre devolver un nuevo objeto.
Cualquier consejo sería apreciado. Nunca he tomado el código OO y lo he refactorizado en código funcional antes.
fuente
Respuestas:
Después de haber creado varios proyectos en JavaScript con estilo funcional, me permite compartir mi experiencia. Me enfocaré en consideraciones de alto nivel, no en preocupaciones específicas de implementación. Recuerde, no hay balas de plata, haga lo que funcione mejor para su situación específica.
Estilo funcional puro versus funcional
Considere lo que desea obtener de esta refactorización funcional. ¿Realmente desea una implementación funcional pura o solo algunos de los beneficios que puede aportar un estilo de programación funcional, como la transparencia referencial?
Encontré el último enfoque, lo que yo llamo estilo funcional, para combinar bien con JavaScript y generalmente es lo que quiero. También permite que los conceptos no funcionales selectivos se utilicen cuidadosamente en el código donde tengan sentido.
Escribir código en un estilo más puramente funcional también es posible en JavaScript, pero no siempre es la solución más adecuada. Y Javascript nunca puede ser puramente funcional.
Interfaces de estilo funcional
En Javascript de estilo funcional, la consideración más importante es el límite entre el código funcional y el código imperativo. Comprender y definir claramente este límite es esencial.
Organizo mi código alrededor de interfaces de estilo funcional. Estas son colecciones de lógica que se comportan en un estilo funcional, que van desde funciones individuales hasta módulos completos. La separación conceptual de la interfaz y la implementación es importante, una interfaz de estilo funcional puede implementarse imperativamente, siempre que la implementación imperativa no se filtre a través del límite.
Desafortunadamente en Javascript, la carga de aplicar interfaces de estilo funcional recae completamente en el desarrollador. Además, las características del lenguaje, como las excepciones, hacen imposible una separación verdaderamente limpia.
Dado que refactorizará una base de código existente, comience por identificar las interfaces lógicas existentes en su código que se puedan mover limpiamente a un estilo funcional. Una sorprendente cantidad de código ya puede ser un estilo funcional. Los buenos puntos de partida son pequeñas interfaces con pocas dependencias imperativas. Cada vez que se mueve sobre el código, se eliminan las dependencias imperativas existentes y se puede mover más código.
Siga iterando, trabajando gradualmente hacia afuera para empujar grandes partes de su proyecto hacia el lado funcional del límite, hasta que esté satisfecho con los resultados. Este enfoque incremental permite probar cambios individuales y facilita la identificación y el cumplimiento de los límites.
Replantear algoritmos, estructuras de datos y diseño
Las soluciones imperativas y los patrones de diseño a menudo no tienen sentido en el estilo funcional. Especialmente para estructuras de datos, los puertos directos de código imperativo al estilo funcional pueden ser feos y lentos. Un ejemplo simple: ¿preferiría copiar cada elemento de una lista para un antecedente o contras el elemento en la lista existente?
Investigue y evalúe los enfoques funcionales existentes a los problemas. La programación funcional es más que una simple técnica de programación, es una forma diferente de pensar y resolver problemas. Los proyectos escritos en los lenguajes de programación funcional más tradicionales, como lisp o haskell, son excelentes recursos a este respecto.
Comprender el lenguaje y las construcciones
Esta es una parte importante de la comprensión del límite imperativo funcional y afectará el diseño de la interfaz. Comprenda qué características se pueden usar de forma segura en el código de estilo funcional y cuáles no. Todo desde el cual los componentes integrados pueden generar excepciones y que, como
[].push
, mutar objetos, a objetos que se pasan por referencia y cómo funcionan las cadenas de prototipos. También se requiere una comprensión similar de cualquier otra biblioteca que consuma en su proyecto.Tal conocimiento permite tomar decisiones de diseño informadas, como cómo manejar las malas entradas y excepciones, o ¿qué sucede si una función de devolución de llamada hace algo inesperado? Algo de esto puede aplicarse en el código, pero algunos solo requieren documentación sobre el uso adecuado.
Otro punto es cuán estricta debería ser su implementación. ¿Realmente quiere intentar imponer el comportamiento correcto en los errores de pila de llamadas máximas o cuando el usuario redefine alguna función incorporada?
Uso de conceptos no funcionales donde sea apropiado
Los enfoques funcionales no siempre son apropiados, especialmente en un lenguaje como JavaScript. La solución recursiva puede ser elegante hasta que comience a obtener la máxima cantidad de excepciones de la pila de llamadas para entradas más grandes, por lo que quizás sería mejor un enfoque iterativo. Del mismo modo, uso la herencia para la organización del código, la asignación en constructores y los objetos inmutables donde tienen sentido.
Mientras no se violen las interfaces funcionales, haga lo que tenga sentido y lo que será más fácil de probar y mantener.
Ejemplo
Aquí hay un ejemplo de conversión de un código real muy simple a un estilo funcional. No tengo un muy buen ejemplo grande del proceso, pero este pequeño toca varios puntos interesantes.
Este código construye un trie a partir de una cadena. Comenzaré con un código basado en la sección superior del módulo trie-js build-trie de John Resig . Se han realizado muchas simplificaciones / cambios de formato y mi intención no es comentar sobre la calidad del código original (Hay formas mucho más claras y limpias de construir un trie, por lo que es interesante por qué este código de estilo c aparece primero en Google. Aquí hay un Implementación rápida que utiliza reducir ).
Me gusta este ejemplo porque no tengo que llevarlo a una implementación funcional verdadera, solo a interfaces funcionales. Aquí está el punto de partida:
Supongamos que este es el programa completo. Este programa hace dos cosas: convertir una cadena en una lista de palabras y construir un trie a partir de la lista de palabras. Estas son las interfaces existentes en el programa. Aquí está nuestro programa con las interfaces más claramente expresadas:
Simplemente definiendo nuestras interfaces, podemos pasar
_get_words
al lado del estilo funcional del límite. Nireplace
osplit
modifica la cadena original. Y nuestra interfaz pública tambiénbuild_trie
es de estilo funcional, aunque interactúa con un código muy imperativo. De hecho, este es un buen punto de parada en la mayoría de los casos. Más código se volvería abrumador, así que permítanme ver algunos otros cambios.Primero, haga que todas las interfaces sean funcionales. Esto es trivial en este caso, solo tiene que
_build_trie_from_list
devolver un objeto en lugar de mutar uno.Manejo de entrada incorrecta
Considere lo que sucede si llamamos
build_trie
con una serie de personajes,build_trie(['a', ' ', 'a', 'b', 'c', ' ', 'f'])
. La persona que llama asumió que esto se comportaría en un estilo funcional, pero en cambio arroja una excepción cuando.replace
se llama en la matriz. Este puede ser el comportamiento previsto. O podríamos hacer verificaciones de tipo explícitas y asegurarnos de que las entradas son como esperábamos. Pero prefiero escribir pato.Las cadenas son solo matrices de caracteres y las matrices son solo objetos con una
length
propiedad y números enteros no negativos como claves. Entonces, si refactorizamos y escribimos nuevosreplace
ysplit
métodos que operan en objetos de cadenas similares a una matriz, ni siquiera tienen que ser cadenas, nuestro código podría hacer lo correcto. (String.prototype.*
no funcionará aquí, convierte la salida en una cadena). La escritura de pato es una programación totalmente separada de la funcional, pero el punto más importante es que la entrada incorrecta siempre debe considerarseRediseño fundamental
También hay consideraciones más fundamentales. Supongamos que también queremos construir el estilo funcional. En el código imperativo, el enfoque consistía en construir el trie palabra por palabra. Un puerto directo nos haría copiar todo el archivo cada vez que necesitemos hacer una inserción para evitar la mutación de objetos. Claramente eso no va a funcionar. En cambio, el trie podría construirse nodo por nodo, de abajo hacia arriba, de modo que una vez que se complete un nodo, nunca más se tenga que tocar. O bien, otro enfoque por completo puede ser mejor, una búsqueda rápida muestra muchas implementaciones funcionales existentes de intentos.
Espero que el ejemplo aclare un poco las cosas.
En general, creo que escribir código de estilo funcional en Javascript es un esfuerzo valioso y divertido. Javascript es un lenguaje funcional sorprendentemente capaz, aunque demasiado detallado en su estado predeterminado.
Buena suerte con tu proyecto.
Algunos proyectos personales escritos en estilo funcional Javascript:
fuente