27 septiembre 2011

Tip rápido para los que usan Mac

Si usan MacOSX y tienen Growl, les recomiendo instalar el extra de growlnotify (esta en la carpeta Extras de la imagen de instalación de Growl).

Es muy util para notificar cuando termina una tarea de esas que llevan tiempo como un build o un update, por ejemplo:

svn update && growlnotify -m "Termino el update"
mvn clean install && growlnotify -m "Termino el build"

14 septiembre 2011

Herencia en JavaScript

JavaScript es en esencia un lenguaje basado en prototipos, que sufre de personalidades múltiples: tiene algunos operadores y comportamientos de un lenguaje con clasificación.

Para entender esto olvidemos por un momento a JavaScript.

Un lenguaje orientado a prototipos -como por ejemplo Self- se basa en que uno obtiene nuevas instancias a partir de la clonación de otras, por ejemplo en un "pseudo-JavaScript":

var auto = {
     acelerarACienEnUnSegundo: function() { return 'No puedo'; },
     arrancar: function() { return 'brummm... :)' }
};

var ferrari = clonar(auto);
ferrari.acelerarACienEnUnSegundo = function() { return 'fiuuummm'; };
ferrari.arrancar(); // retorna brummm....

¿Donde esta la esquizofrenia de JavaScript? Esta en que contamos con un operador new que simula comportarse como si estuviésemos trabajando con un lenguaje con clasificación, por ejemplo:

var auto = new Auto();
function Auto() {
     this.arrancar = function() { return 'brummm....'; };
}

¿Qué hace new?
Cada función que uno define en JavaScript es un objeto que tiene asociada una propiedad prototype.
Es como si cuando uno escribe function Auto() {} escribiera:

var Auto = {
    prototype: {}, 
    constructor: function() { /* cuerpo de la función Auto */ } };

Cuando se usa el operador new JavaScript lo que hace es:

  • Clonar el objeto prototype
  • Después evaluar la función en constructor usando la nueva instancia como contexto (es decir this es el clon de prototype)

Es por eso que uno podría escribir la función Auto como:

function Auto() { console.debug(this.arrancar); }
Auto.prototype.arrancarfunction() { return 'brummm....'; }; 

Esto es muy confuso, por que pareciera que una función se puede comportar como si fuese una clase. De hecho genera más confusión aún cuando uno quiere compartir implementación usando algo similar a la herencia.

Sin embargo el problema es más simple de resolver: no hay clases ni tipos, solo tenemos que agregar los métodos que necesitamos compartir, por ejemplo:

var agregarComportamientoDeAuto = function(obj) {
      obj.arrancar =  function() { return 'brummm... :)' };
}

¿Como creamos un objeto que se comporte como un auto?

var ferrari = function() {
      var nuevaInstancia = {};
      agregarComportamientoDeAuto(nuevaInstancia);
      nuevaInstancia.acelerarACienEnUnSegundo = function() { return 'fiuuummm'; };
};

Quizás no luzca muy elegante, pero en mi opinión es mejor que simular un lenguaje con clasificación donde no lo hay.
La ventaja de este método es que es muy fácil crear mixins o tener variables privadas (en el ejemplo podríamos pasar argumentos adicionales a agregarComportamientoDeAuto y aprovechar las clausuras).

También es posible simular delegación estilo "super", la próxima voy a dar un par de ejemplos de eso, pero adelanto que es una solución muy poco elegante.

02 septiembre 2011

Some Java "dependency injection" bad practices

When Spring and other dependency injection frameworks appeared on the Java space, some of its goals where to be "non-invasive" and to facilitate unit testing (see this old Rod Johnson presentation).

But when DI frameworks started to take advantage of annotations introduced in Java 5, the term "non-invasive" became relative, why? Here are some reasons:

@Autowire and @Inject annotation in private fields/private constructors

Spring, CDI and Guice allows you to annotate private fields, to let the DI framework inject the appropriate instance. But this feature has two problematic consequences, lets see them in a example taken from the Jboss CDI documentation:

public class Checkout {
       private @Inject ShoppingCart cart;
}


Consequences:

  1. Is impossible to create a valid Checkout instance without using a framework that supports the @Inject annotation. 
  2. You would not see the dependency directly by looking into the Checkout public interface or JavaDocs.
(1) is bad because your code becomes more coupled with the DI framework. For example if you want to create an unit test that uses a Checkout instance, you have to set up the DI framework first.

There are annotations and test classes to make tests for Spring beans, but in my experience (after having to maintain application-context.xml files created and used by multiple tests) is an unnecessary complexity that could be removed if the code is not coupled with the DI framework.

Some people argue that (2) is good (for example: this Guice "best practice"). The idea behind that is that developers will do "bad things" if they can: instead of obtaining the required instance by using dependency injection, they will use the constructor.
But that has more to do with a communication problem, and should be resolved by using code reviews, pair programming and walkthroughs.

@Required
In the same tone as the @Inject for private fields comes the @Required annotation supported by Spring. That annotation allows you to write things like:

public class Shape {
       private Color color;
       @Required
       public void setColor(Color c) {
            color = c;
       }
       ....
}

Then the DI framework will give you an exception if the dependency is missing.

But what happens if you instantiate a Shape outside the DI framework? You don't know if you have to set the color or not.

A constructor is simple: no dependencies with the DI framework and comes free with the Java language :) (This part from the "famous" Martin Fowler article on DI, also talks about constructors and setters in DI frameworks)

Note for the reader: I wrote the last articles in English because I wanted to practice that language. But I think that is better to keep the blog posts in one language, and Spanish is my native language, that's why this will be my last post in English (at least in this blog).