Uno de los grandes problemas que existe con JavaScript, ergo con CoffeeScript, es que no importa el numero de ficheros que tengas tu proyecto. JavaScript solo sabe de lineas y por lo tanto existe la posibilidad de que El Libre Albedrío haga acto de presencia, creando estructuras incorrectas, inmantenibles y peligrando el GlobalScope. Se que esto puede darse en todos los lenguajes de programación, pero bajo mi punto de vista JavaScript Todos estos problemas crecen exponencialmente cuando es un equipo el que se enfrenta a un proyecto en JavaScript/CoffeeScript, tenemos/tienes que solucionarlo.
En este capítulo he intentado reunir algunos puntos interesantes que tienes que tener en cuenta para tener un código organizado y mantenible; independientemente del patrón de diseño que utilices.
Como ya vimos en capítulos anteriores, todas las variables que se declaran en una función solo existen en el ámbito propio de la función. Cada nuevo fichero que compila CoffeeScript a su vez genera una función autoejecutable, con su propio ámbito, y por lo tanto si tenemos dos ficheros las variables de un fichero no serán visibles desde el otro, a menos que estén declaradas en el GlobalScope. Por una parte esta bien pensado ya que a menos que lo queramos hacer consecuentemente, tu GlobalScope quedará limpio y sin saber de tus mil y una funciones.
Bien, y ahora te pregunto ¿cómo podemos acceder a esas funciones y variables que tenemos en módulos separados y fuera del GlobalScope?; muy sencillo. Debemos declarar una única variable global, en browsers asignarla al objeto window
y en proyectos NodeJS al objeto global
. Veamos como:
(global or window).libjs = {}
Tan sencillo como lo que ves, nos aseguramos que nuestro objeto libjs
va a declararse en entornos browser o NodeJS. A partir de ahora ya podemos crear nuestros diferentes módulos utilizando el namespace libjs
:
math.coffee
libjs.math =
sum : (a, b) -> a + b
rest: (a, b) -> a - b
# ... #
constants.coffee
libjs.CONST =
MIN_VALUE: 10
MAX_VALUE: 90
class.coffee
class libjs.Hero
Este es un primer acercamiento a como tener organizados tus fuentes creando un sistema de Namespaces, de todas formas, te recomiendo que junto al Namespacing deberás utilizar algún patrón de diseño; siempre adecuado a la naturaleza de tu proyecto.
Los llamados mixins no están soportados de forma nativa en CoffeeScript, por la sencilla razón de que son triviales y cada desarrollador hace su propia implementación; algo parecido a lo que pasa con el patrón MVC. En el siguiente ejemplo
extend = (obj, mixin) ->
obj[name] = method for name, method of mixin obj
include = (class_reference, mixin) ->
extend class_reference.prototype, mixin
include Parrot, isDeceased: true
(new Parrot).isDeceased
Los mixins son un buen patrón para compartir lógica de negocio común entre diferentes módulos de tu aplicación. La ventaja de los mixins es que puedes incluirlo en más de un objeto o clase, mientras que comparandolo con la herencia de una clase solo puede heredar de un único origen.
Una cosa que aprendí cuando lei mi primer libro sobre CoffeeScript, escrito por Alex MacCaw, fue que los mixins no son suficientes ya que no estan muy orientados a objetos. Necesitamos una mejor manera de integrar mixins en nuestras clases CoffeeScript, y Alex pensó en crear una clase base Module
de la que el resto de clases extendiensen. La transcribo tal cual el lo pensó:
KEYWORDS = ['extended', 'included']
class Module
@extend: (obj) ->
for key, value of obj when key not in KEYWORDS
@[key] = value
obj.extended?.apply(@)
@
@include: (obj) ->
for key, value of obj when key not in KEYWORDS
@::[key] = value
obj.included?.apply(@)
@
Como vemos crea dos funciones estáticas extend
e include
las cuales reciben un objeto, que en este caso será una secuencia de nombres de función y su función. La función extend
se encargará de generar las funciones estáticas y la función include las funciones de instancia, facil. Ahora vamos a ponerlo en práctica, imagina que tienes un modulo que genera IDs aleatorios:
libjs.guid.coffee
libjs.guid =
generate: ->
'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace /[xy]/g, (c) ->
r = Math.random() * 16 | 0
v = if c is 'x' then r else r & 3 | 8
v.toString 16
.toUpperCase()
El módulo libjs.guid
y su función generate
lo vamos a incluir dentro una nueva clase Hero
extendida de Module
, y la vamos a utilizar en el constructor
que se encargará de con cada instancia tenga un atributo id
que lo identifica:
class Hero extends Module
@include libjs.guid
constructor: (@name) ->
@id = @generate()
console.log "Instance with id #{@id}"
Parece sencillo no?, vamos a ponerlo en práctica creando un par de instancias:
batman = new Hero "Batman"
> 'Instance with id 04C1E095-E1B9-475F-9D8D-48BD931AAD04'
superman = new Hero "Superman"
> 'Instance with id 1EC75599-6CA7-490B-B3C1-F68CF4688814'
Como vemos la clase Module
que pensó Alex McCaw es tremendamente fácil de implementar y extensible horizontalmente ya que puedes tener modulos iguales que se incluyen en clases diferentes, generando un código así mucho más mantenible. Por ponerte un ejemplo mis proyectos Monocle y Atoms utilizan la class Module
y utilizan modulos comunes, a pesar de proyectos diferentes.