18 de noviembre de 2010

Principio Abierto-Cerrado - SOLID

Continuando con la serie prometida de posts sobre principios SOLID, hoy voy a presentar el principio OCP, Open-Closed Principle.

La definición del principio dice lo siguiente:

Las entidades de software (clases, módulos, métodos, etc.) deben estar abiertas para extensión, pero cerradas para modificación.

Dicho de otra forma, esas entidades deben estar diseñadas de tal manera que su comportamiento pueda ser alterado o extendido sin modificar el código fuente, logrando así que la entidad de software quede protegida contra el cambio.

Dicho así puede sonar extraño, pero para entender más fácilmente este principio hay que tener en cuenta dos cosas, la primera es la más obvia y es qué es lo que quiero proteger?, todo un assembly, una clase determinada o un método particular? y la segunda y no tan obvia es, de qué lo quiero proteger?

Voy a presentar un ejemplo basado en la clase Factura de mi post previo sobre SRP que, espero, sirva para aclarar un poco.

Supongamos que mi clase Factura tiene un método Enviar que se encarga de enviar una copia de la factura al cliente y otra copia al sector de administración, pero estas copias son enviadas de distintas formas (por correo, por email, por fax, etc.)

Por un lado tengo un enumerado que me indica el método de envío:


public enum MetodoEnvio
{
eMail,
Correo,
Fax
}


Por otro lado, siguiendo el principio de responsabilidad única (SRP) tengo clases que se encargan de realizar el envío, los despachadores:


public class CorreoDespachadorFacturas
{
public CorreoDespachadorFacturas(Destino destino)
{
// guardar destino en un field
}

public void Despachar(Factura factura)
{
throw new NotImplementedException();
}
}

public class EMailDespachadorFacturas
{
public EMailDespachadorFacturas(Destino destino)
{
// guardar destino en un field
}

public void Despachar(Factura factura)
{
throw new NotImplementedException();
}
}

public class FaxDespachadorFacturas
{
public FaxDespachadorFacturas(Destino destino)
{
// guardar destino en un field
}

public void Despachar(Factura factura)
{
throw new NotImplementedException();
}
}


Y finalmente tengo mi clase Factura y un ejemplo simple para invocar al método Enviar de la factura:


public class Factura
{
public void Enviar(MetodoEnvio metodoEnvioCopiaCliente, Destino destinoCopiaCliente,
MetodoEnvio metodoEnvioCopiaAdministracion, Destino destinoCopiaAdministracion)
{
enviarCopia(metodoEnvioCopiaCliente, destinoCopiaCliente);
enviarCopia(metodoEnvioCopiaAdministracion, destinoCopiaAdministracion);
}

private void enviarCopia(MetodoEnvio metodoEnvio, Destino destino)
{
switch (metodoEnvio)
{
case MetodoEnvio.eMail:
var despachadorEMail = new EMailDespachadorFacturas(destino);
despachadorEMail.Despachar(this);
break;
case MetodoEnvio.Correo:
var despachadorCorreo = new CorreoDespachadorFacturas(destino);
despachadorCorreo.Despachar(this);
break;
case MetodoEnvio.Fax:
var despachadorFax = new FaxDespachadorFacturas(destino);
despachadorFax.Despachar(this);
break;
}
}

//..
}

public class EjemploUsoFactura
{
public void EnviarFacturas(IEnumerable<factura> facturas)
{
foreach (var factura in facturas)
factura.Enviar(MetodoEnvio.Correo, new Destino { /* domicilio del cliente */ },
MetodoEnvio.eMail, new Destino { /* domicilio de la administracion */ });
}

//...
}


Respondamos las dos preguntas planteadas más arriba:

1) Qué quiero proteger?

Quiero proteger el método Enviar de la clase Factura

2) De qué quiero proteger al método Enviar?

Para soportar nuevos métodos de envío que se agreguen en el futuro, tal como está diseñado el método Enviar, debería abrir su código y agregar un nuevo case al switch, por lo tanto no estaría cumpliendo OCP, por lo tanto la respuesta sería "Quiero protegerlo ante el agregado de nuevos métodos de envío"

Una forma de hacer esto es creando una interfaz IDespachadorFacturas y haciendo que esta interfaz sea implementada por todos mis despachadores concretos:


public interface IDespachadorFacturas
{
void Despachar(Factura factura);
}

public class CorreoDespachadorFacturas : IDespachadorFacturas
{
public CorreoDespachadorFacturas(Destino destino)
{
// guardar destino en un field
}

#region IDespachadorFacturas Members

public void Despachar(Factura factura)
{
throw new NotImplementedException();
}

#endregion
}

public class EMailDespachadorFacturas : IDespachadorFacturas
{
public EMailDespachadorFacturas(Destino destino)
{
// guardar destino en un field
}

#region IDespachadorFacturas Members

public void Despachar(Factura factura)
{
throw new NotImplementedException();
}

#endregion
}

public class FaxDespachadorFacturas : IDespachadorFacturas
{
public FaxDespachadorFacturas(Destino destino)
{
// guardar destino en un field
}

#region IDespachadorFacturas Members

public void Despachar(Factura factura)
{
throw new NotImplementedException();
}

#endregion
}


De esta forma nos abstraemos de cuál sea el método de envío ya que simplemente desde la Factura "hablaremos" con la interfaz IDespachadorFacturas:


public class Factura
{
public void Enviar(IDespachadorFacturas despachadorFacturaCopiaCliente,
IDespachadorFacturas despachadorFacturaCopiaAdministracion)
{
despachadorFacturaCopiaCliente.Despachar(this);
despachadorFacturaCopiaAdministracion.Despachar(this);
}

//...

}

public class EjemploUsoFactura
{
public void EnviarFacturas(IEnumerable<factura> facturas)
{
IDespachadorFacturas despachadorCopiaCliente =
new CorreoDespachadorFacturas(new Destino { /* domicilio del cliente */ });
IDespachadorFacturas despachadorCopiaAdministracion =
new EMailDespachadorFacturas(new Destino { /* domicilio de la administracion */ });

foreach (var factura in facturas)
factura.Enviar(despachadorCopiaCliente, despachadorCopiaAdministracion);
}

//...
}


Podríamos terminar aquí ya que hemos cumplido en proteger al método Enviar del agregado a futuro de nuevos métodos de envío, pero miremos un poco más como ha quedado nuestro diseño:


public void Enviar(IDespachadorFacturas despachadorFacturaCopiaCliente,
IDespachadorFacturas despachadorFacturaCopiaAdministracion)
{
despachadorFacturaCopiaCliente.Despachar(this);
despachadorFacturaCopiaAdministracion.Despachar(this);
}


Qué pasaría si en el futuro se presenta la necesidad de enviar una tercer copia de la factura a Tesorería? el método Enviar no está protegido contra este cambio, ya que tendría que abrir el código fuente del método y agregar algo así:


public void Enviar(IDespachadorFacturas despachadorFacturaCopiaCliente,
IDespachadorFacturas despachadorFacturaCopiaAdministracion,
IDespachadorFacturas despachadorFacturaCopiaTesoreria)
{
despachadorFacturaCopiaCliente.Despachar(this);
despachadorFacturaCopiaAdministracion.Despachar(this);
despachadorFacturaCopiaTesoreria.Despachar(this);
}


Para solucionar este problema podríamos hacer lo siguiente:


public class Factura
{
public void Enviar(IEnumerable<idespachadorfacturas> despachadoresFacturas)
{
foreach (var despachador in despachadoresFacturas)
despachador.Despachar(this);
}

//...
}

public class EjemploUsoFactura
{
public void EnviarFacturas(IEnumerable<factura> facturas)
{
IDespachadorFacturas despachadorCopiaCliente =
new CorreoDespachadorFacturas(new Destino { /* domicilio del cliente */ });
IDespachadorFacturas despachadorCopiaAdministracion =
new EMailDespachadorFacturas(new Destino { /* domicilio de la administracion */ });
IDespachadorFacturas despachadorCopiaTesoreria =
new FaxDespachadorFacturas(new Destino { /* domicilio de la tesoreria */ });

var despachadores = new List<idespachadorfacturas>
{ despachadorCopiaCliente, despachadorCopiaAdministracion, despachadorCopiaTesoreria };
foreach (var factura in facturas)
factura.Enviar(despachadores);
}

//...
}


Y de esta forma tendremos protegido al método Enviar de la factura ante dos posibles cambios:
  • El agregado de nuevos métodos de envío
  • La necesidad de enviar más copias a otros destinatarios.

Pero todavía hay un poco más, hay otra forma de solucionar este último problema que se nos presentó y es aplicando un patrón de diseño llamado Composite.

Creamos un nuevo despachador (el Composite) que va a contener todos los despachadores que queramos utilizar para enviar facturas, y pasamos ese despachador al método Enviar.

Luego será el despachador composite el encargado de llamar al método Despachar de cada despachador:


public class CompositeDespachadorFacturas : IDespachadorFacturas
{
public void AgregarDespachador(IDespachadorFacturas despachador)
{
_despachadores.Add(despachador);
}

public void EliminarDespachador(IDespachadorFacturas despachador)
{
throw new NotImplementedException();
}

private readonly IList<idespachadorfacturas> _despachadores = new List<idespachadorfacturas>();

#region IDespachadorFacturas Members

void IDespachadorFacturas.Despachar(Factura factura)
{
foreach (IDespachadorFacturas despachador in _despachadores)
despachador.Despachar(factura);
}

#endregion
}



public class Factura
{
public void Enviar(IDespachadorFacturas despachadorFacturas)
{
despachadorFacturas.Despachar(this);
}

//...
}

public class EjemploUsoFactura
{
public void EnviarFacturas(IEnumerable<factura> facturas)
{
CompositeDespachadorFacturas composite = new CompositeDespachadorFacturas();

IDespachadorFacturas despachadorCopiaCliente =
new CorreoDespachadorFacturas(new Destino { /* domicilio del cliente */ });
composite.AgregarDespachador(despachadorCopiaCliente);

IDespachadorFacturas despachadorCopiaAdministracion =
new EMailDespachadorFacturas(new Destino { /* domicilio de la administracion */ });
composite.AgregarDespachador(despachadorCopiaAdministracion);

IDespachadorFacturas despachadorCopiaTesoreria =
new FaxDespachadorFacturas(new Destino { /* domicilio de la tesoreria */ });
composite.AgregarDespachador(despachadorCopiaTesoreria);

foreach (var factura in facturas)
factura.Enviar(composite);
}

//...
}


Cuál es la ventaja de esta última solución? de esta forma el método enviar de la factura se abstrae al máximo posible de la forma en que se despachan las mismas, es decir, la factura sabe que tiene que hablar con un despachador que tiene un único método Despachar, luego la forma en que se despache la factura, los destinos a los cuáles será enviada y también la cantidad de veces que la misma será despachada es totalmente transparente para la factura.
Esto minimiza la posibilidad de que en el futuro algún cambio a la funcionalidad de mi aplicación impacte de forma tal, que tenga que abrir el código del método enviar de la factura y modificarlo.

Por último, es importante destacar que la decisión de aplicar OCP sobre un método, clase, etc. debe ser una decisión estratégica basándonos en nuestra experiencia, ya que sería imposible intentar cerrar por completo nuestras entidades de software. Será en aquellos casos donde se detecte que hay una gran probabilidad de cambio donde tendremos que aplicar OCP para mejorar la mantenibilidad de nuestro sistema.

En mi post anterior sobre el principio SRP podrán encontrar varios enlaces a las lecturas recomendadas sobre principios SOLID.

Espero que sea de utilidad, cualquier comentario será bienvenido.

18 de junio de 2010

Principio de responsabilidad única - SOLID

Los principios SOLID son una serie de herramientas o técnicas introducidas por Robert C. Martin a principios de 2000, los cuales aplicados en conjunto deberían llevar a obtener diseños que sean mas mantenibles, flexibles y extensibles.

SOLID es un acrónimo formado por la primer letra del nombre de cada uno de los cinco principios, los cuales son:

SRP, alias Single responsibility principle, alias Principio de responsabilidad única

OCP, alias Open/closed principle, alias Principio de abierto/cerrado

LSP, alias Liskov substitution principle, alias Principio de substitución de Liskov

ISP, alias Interface segregation principle, alias Principio de separación de interfaces

DIP, alias Dependency inversion principle, alias Principio de inversión de dependencias

Para obtener el mayor beneficio de estos principios, los mismos se deberían aplicar en conjunto, ya que unos refuerzan a otros (algo así como las prácticas de extreme programming, las cuales también se refuerzan entre sí)

Como por algún lado hay que empezar, en este primer post vamos a tratar el principio de responsabilidad única (SRP)

La definición del principio de responsabilidad única dice lo siguiente:

Nunca debería haber más de un motivo por el cual una clase deba cambiar

Que significa esto? si pensamos en el concepto de responsabilidad como tareas que se le asignan a una clase, imaginemos que tenemos una clase con mas de una tarea por realizar, es más, imaginemos que tiene muchas tareas que realizar, por ejemplo pensemos en una clase Factura donde cada método de la misma es una tarea que la Factura realiza:



NOTA: claro que a esta clase Factura le falta información básica como fecha, número, etc... pero para este ejemplo vamos a obviar esta información

Ahora pensemos qué pasa si queremos cambiar la forma en que se imprime la Factura, esto implica cambiar mi clase Factura, que pasaría si quiero cambiar la forma en que se calculan los descuentos o los impuestos, también esto implica cambiar mi clase factura, y así podríamos seguir...

Qué nos dice esto? que la clase Factura tiene muchos motivos por los cuales podría cambiar. Veamos lo que se dice en el libro "Head First Design Patterns" relacionado con el tema del cambio.

No importa donde estés trabajando, qué estés construyendo o qué lenguaje de programación estés usando, cual es la única y verdadera constante que siempre va a estar con nosotros? El CAMBIO, es decir, no importa qué tan bien diseñes tu aplicación, a medida que pase el tiempo una aplicación deberá crecer y cambiar o inevitablemente morirá.

Parece que es importante que una aplicación esté diseñada para afrontar el cambio...

Imaginemos que GenerarReporte, en la versión inicial de nuestra aplicación, lo único que hacía era generar en formato de texto plano la información contenida dentro de la factura, pero a medida que pasa el tiempo, por requerimientos de nuestro cliente, se decide realizar un cambio y que el reporte sea mas elaborado y además se genere en formato PDF. Esto significaría referenciar desde nuestra clase Factura a librerías de generación de PDF, lo cual hace que nuestra clase se termine acoplando a una librería que sólo es necesaria para generar un reporte y no para calcular descuentos, subtotales, etc...

Si nuestra clase factura la estuviéramos utilizando desde una aplicación que realiza procesamientos en forma desatendida, esto implicaría que nuestra aplicación debería incluir librerías para generar PDF, aún cuando nunca fuera necesario utilizarlas (pensemos que ocurriría si además tuviéramos que pagar una licencia adicional para instalar esta librería en la máquina que corre procesos batch ;)

De haber aplicado SRP en nuestro diseño, quizás este problema se hubiera evitado? Veamos si esto se cumple

Por ahora apliquemos SRP sólo para solucionar este problema particular, mas adelante rediseñaremos otras partes de la clase Factura





De esta manera, la clase Factura puede ser utilizada en forma desacoplada del generador de reportes y, por lo tanto, de la librería de generación de PDF.


public class FacturaPrinter
{
public void ImprimirFactura(Factura factura)
{
string ruta = getRutaGeneracion();

var generadorReporteFactura = new GeneradorReporteFactura();
generadorReporteFactura.GenerarReporte(ruta, factura);

imprimir(ruta);
}

private string getRutaGeneracion()
{
//obtengo la ruta donde se debe generar la factura
}

private void imprimir(string rutaReporte)
{
//imprimo el reporte generado
}
}


Esto fue logrado ya que le hemos quitado a la clase Factura la responsabilidad de saber imprimirse y hemos delegado esa responsabilidad en una nueva clase GeneradorReporteFactura.

SRP nos salvó de tener que pagar licencias adicionales de la librería para generación de PDF!!

Veamos que SRP no solo minimiza el impacto ante los cambios, sino que es útil en otros aspectos.

Testeo unitario

Imaginemos que el método CalcularDescuentos está programado de tal manera que llama internamente al método CalcularSubtotal, y en base a ese valor determina los descuentos a aplicar (por ej, por montos superiores a $1000 se realiza un descuento del 5%). Luego, en forma adicional, también se calculan otros descuentos en base a otras condiciones, como por ejemplo si el cliente es preferencial, si la forma de pago es en efectivo, etc...

Para poder hacer un testeo unitario que sólo verifique el algoritmo de CalcularDescuentos, necesito armar una instancia de factura que contenga lineas con datos como la cantidad, el precio unitario, etc... de forma tal que cuando se llame a CalcularSubtotal, el método me dé un valor X, para luego poder realizar el cálculo de los descuentos. Qué ocurre si además el constructor de líneas de factura necesita que le pasemos una instancia de un Producto, el cual a su vez es complejo de generar? Digamos que cada vez se hace mas complejo realizar un testeo unitario del método CalcularDescuentos...

Apliquemos SRP para intentar solucionar este problema



En este caso, a diferencia del caso anterior, el calculador de descuentos es una dependencia de la factura, de esta manera Factura.CalcularDescuentos delega el cálculo a la clase CalculadorDescuentosFactura.


public class Factura
{
public Factura(CalculadorDescuentosFactura calculadorDescuentosFactura)
{
_calculadorDescuentosFactura = calculadorDescuentosFactura;
}

private CalculadorDescuentosFactura _calculadorDescuentosFactura;

public void CalcularDescuentos()
{
Descuentos = _calculadorDescuentosFactura.CalcularDescuentos(this);
}

// ....
}


Luego, a la hora de hacer un testeo unitario del método CalculadorDescuentosFactura.CalcularDescuentos, me alcanza con pasar como parámetro un mock de la clase Factura (un objeto factura falso) configurado de forma tal, que devuelva un valor X definido por mi cuando el método CalcularSubtotal sea llamado.
NOTA: cabe aclarar que para poder realizar esto, Factura debería ser una abstracción (una interfaz, por ejemplo) y no una clase concreta. De esta forma se podría substituir por un mock de la misma. De esto vamos a hablar cuando tratemos los otros principios SOLID

En este caso SRP nos ayudó a tener un código que es mas fácil de testear.

Claridad del código

Pensemos que existe la remota posibilidad de que alguien necesite modificar la forma en que se calculan los impuestos de una factura y esa persona no es quien originalmente programó la clase... o efectivamente es quien la programó pero lo hizo hace tiempo y ya no se acuerda cómo estaba diseñada la clase. (suele pasar, no? ;)

Esta persona deberá recorrer toda la clase factura, tratando de ubicar en qué parte de la misma está la lógica de cálculo de impuestos. En el ejemplo esta tarea parece bastante fácil, ya que hay un método CalcularImpuestos, pero pensemos qué pasaría si el cálculo de impuestos es más complejo y no fuera tan fácil de identificar esta lógica.

Aplicando SRP podríamos aislar esta lógica en una clase separada, y de esta forma sería mucho mas fácil de entender y seguir el diseño. Este caso es similar al del calculador de descuentos, así que aplicaremos la misma solución.




public class Factura
{
public Factura(CalculadorDescuentosFactura calculadorDescuentosFactura,
CalculadorImpuestosFactura calculadorImpuestosFactura)
{
_calculadorImpuestosFactura = calculadorImpuestosFactura;
_calculadorDescuentosFactura = calculadorDescuentosFactura;
}

private CalculadorDescuentosFactura _calculadorDescuentosFactura;
private CalculadorImpuestosFactura _calculadorImpuestosFactura;

public void CalcularImpuestos()
{
Impuestos = _calculadorImpuestosFactura.CalcularImpuestos(this);
}

// ....
}


SRP es un principio que nos permite tener diseños que respondan bien ante cambios, fáciles de testear y claros, asi que la pregunta que podríamos plantear ahora es, debo aplicar este principio SIEMPRE?

Si llevamos SRP al extremo, terminaríamos con clases que sólo tengan un único método, lo cual tampoco es recomendable, ya que estaríamos incrementando la complejidad de nuestro diseño (pensemos que se incrementa altamente la cantidad de dependencias entre clases y esto no es algo "gratis"). Es por esto que, como todo principio SOLID, siempre es importante tenerlo presente para identificar posibles falencias que pueda tener nuestro diseño, y luego decidir en cada caso puntual si es conveniente aplicarlo o no. (si estamos aprendiendo, ante la duda quizás lo recomendable sea aplicar el principio, luego la experiencia nos dirá...)

Algunos "olores" que nos pueden indicar que conviene aplicar SRP


  • Tengo clases muy extensas que hacen de todo
  • Cuando toco código asociado a una funcionalidad A, se termina rompiendo la funcionalidad B
  • Cuando toco código asociado a una funcionalidad A, tengo que testear las funcionalidades A, B y C porque no sé qué se podría romper
  • Tengo clases con nombres como FacturaManager, FacturaHandler, FacturaAdmin, etc... (si tienen esos nombres muy probablemente es porque no quedaba clara cual era su responsabilidad a la hora de nombrarlas)


Por último dejo algunos links a material de referencia sobre el principio SRP y principios SOLID en general, en el próximo post vamos a estar tratando el principio abierto/cerrado (OCP)

Grupo de estudio sobre principios SOLID del cual participo en la comunidad Alt.net hispano, con la coordinación de Fernando Claverino

SOLID - Wikipedia

Libro Head first object-oriented analysis and design (en el capítulo 8 se tratan varios principios de diseño, incluyendo SRP)

Artículos de Robert C. Martin sobre principios SOLID

Libro Agile principles, patterns, and practices in C# de Robert C. Martin

Espero que sea de utilidad, cualquier comentario será bienvenido.

8 de junio de 2010

Nueva versión de NHTrace

Hay una nueva versión de NHTrace con dos nuevas funcionalidades:

  • Copiar comandos sql formateados para que sean más legibles
  • Copiar comandos y reemplazar automáticamente los parámetros que tuvieran los mismos




"Copy formatted sql" guarda lo siguiente en el portapapeles

INSERT
INTO
"Product"
(Name, UnitPrice, Discontinued, Id)
VALUES
(@p0, @p1, @p2, @p3);
@p0 = 'Apple0', @p1 = 0.25, @p2 = False, @p3 = 1001


"Copy sql and replace parameters" guarda lo siguiente en el portapapeles

INSERT
INTO
"Product"
(Name, UnitPrice, Discontinued, Id)
VALUES
('Apple0', 0.25, False, 1001);


La nueva versión se puede descargar desde esta ubicación

Quiero aprovechar para agradecerle a Pablo ->

quien me sugirió agregar estas funcionalidades a NHTrace

Espero que sea útil! cualquier comentario será bienvenido.

20 de mayo de 2010

Trace de NHibernate con syntax highlighting

NHTrace es una aplicación open source que muestra las sentencias sql generadas por NHibernate, tal como se podrían ver por consola, pero en este caso con syntax highlight para poder ver de forma mas amigable el código sql generado.



Para resolver el tema de syntax highlighting, a falta de buenas librerías open source en C#, decidí optar por usar librerías javascript y mostrar los mensajes en un componente WebBrowser.

Acá apareció el primer problema, el componente WebBrowser que viene con el framework funciona de forma tal, que cuando se asigna el código html a mostrar, por cuestiones de seguridad no se soporta la ejecución de código javascript. Para resolver esto fue necesario crear una clase que herede del WebBrowser e implementar algunas interfaces para que la seguridad la podamos manejar nosotros y así habilitar la ejecución de javascript.

Por suerte alguien ya había hecho algo así antes, porque seguir la documentación de la MSDN para hacer esto es, con riesgo a quedarme corto, muy confuso.

Para resolver el problema de "escuchar" los eventos de log4net, se presentaban distintas opciones con respecto a qué appender de log4net utilizar. A continuación menciono los appenders que fui probando y el motivo por el cual los descarté:

RemotingAppender: De forma aleatoria, los eventos llegaban desordenados

UdpAppender: De forma aleatoria, algunos eventos se "perdían"

FileAppender: Esta era una opción bastante aceptable, pero no llegué a resolver un problema que se presenta cuando se configura el appender con "appendToFile" en false (probablemente en el futuro le dedique mas tiempo para ver si se puede resolver)

Teniendo en cuenta que ninguno de estos appenders me servían de mucho, gracias a google (cuándo no) encontré que en el repositorio de svn de log4net hay un MsmqAppender que no está incluido en la distribución de log4net, pero realmente funciona perfecto, no pierde eventos y los eventos llegan en el orden correcto. La única desventaja de este appender es que hay que configurar el Message Queuing de Windows, ya que ese es el mecanismo que utiliza para la transmisión de los eventos, pero de todas maneras es algo muy sencillo de hacer.

Para más detalles sobre cómo configurar qué librería utilizar para el syntax highlight, cómo configurar MSMQ y log4net, está todo explicado en el sitio del proyecto, claro que desde ahí también se puede descargar la aplicación.

Espero que sea de utilidad, cualquier comentario ó sugerencia de mejora será bienvenido.

19 de abril de 2010

Document Session Manager - Addin de Visual Studio para administrar documentos

Document Session Manager es un addin para Visual Studio 2008 que permite guardar y recuperar la lista de archivos abiertos dentro de la IDE (ej: archivos xml, archivos de código fuente, formularios winforms, etc.)

Las características de Document Session Manager (DSM de ahora en más) están inspiradas en un addon de firefox llamado Session Manager

DSM permite guardar la lista de archivos abiertos dentro de lo que es llamado una "sesión". Luego, esta sesión puede ser cargada causando que todos los documentos que pertenecen a la misma sean abiertos en la IDE. El objetivo principal de ello es ayudar al programador a focalizarse en sólo un conjunto de documentos, deshaciéndose de otros que se pudieran haber abierto durante un debugging o mientras se estaba navegando mediante el comando Go To Definition (F12) de Visual Studio (en mi experiencia, muchos archivos que quizás sólo necesitábamos para echarles un vistazo rápido, pueden llenar la IDE de Visual Studio en muy poco tiempo ;)

Una característica extra de DSM es la habilidad de mantener la lista de documentos que estaban abiertos antes de iniciar un debugging; una vez que el debugging termina, esa lista de documentos puede ser restaurada, cerrando cualquier otro documento que se hubiese abierto durante la sesión de debug. (NOTA: si se desea, esta característica puede ser desactivada en la página de opciones del addin)

El proyecto está hosteado en codeplex. Más información y capturas de pantalla se pueden encontrar en la página principal del proyecto y en el sector de documentación.

Algunas capturas...





Espero que sea útil, cualquier comentario será bienvenido!

9 de abril de 2010

Plugin de CodeRush para cerrar el archivo activo cuando se recolecta un marcador

CR_MarkerCloseOnCollect es un plugin que he desarrollado y que puede resultar útil para aquellos que, como yo, usan CodeRush como herramienta de refactor en Visual Studio.

Es un plugin muy sencillo (de hecho, está hecho con solo 5 líneas de código), cuyo propósito es cerrar el archivo activo cuando se recolecta un marcador (Marker) de CodeRush.

Hay una excepción a esta regla, ya que cuando el marcador que se recolecta pertenece al mismo archivo que actualmente está activo en la IDE de Visual Studio, el archivo no se cerrará.

Mas allá de la sencillez del plugin, creo que puede ser de bastante utilidad, ya que es muy común utilizar F12 para ir al archivo donde se encuentra la declaración de algún elemento (como puede ser una clase, interfaz, método, etc...) y luego querer volver al lugar donde se insertó el marcador, sin que nos interese mantener abierto este archivo.

El enlace para descargar el plugin y algunas capturas de pantalla se pueden encontrar en esta ubicación, dentro del proyecto DXCore Community Plugins.

Espero que sea útil, cualquier comentario será bienvenido !

25 de marzo de 2010

CodeRush plugin to extract NHibernate's hql queries as Named Queries in a separate xml file

I have created a DevExpress CodeRush plugin to extract hql queries as Named Queries to an xml mapping file.

The idea is to write the hql query in code, like this:


var query = session.CreateQuery("from Order o where o.Amount > :amount");
query.SetParameter("amount", 100);
return query.List<Order>();


Then, position the cursor over the hql query, in this case "from Order o where o.Amount > :amount", and select the refactor option "Extract Hql Named Query".



The plugin then tries to locate the mapping file, and adds the query as a Named query.



After the query is added in the xml file, you can give a name to the named query, which will be synchronized in both files (like que CodeRush's rename F2)



Please note that after the query has been extracted, you should manually change the CreateQuery method with GetNamedQuery

Currently the plugin supports only a fixed name for the xml mapping file, and two strategies to locate that file:

* In current project only (default)
* First in current project, then in solution

This can be configured in the plugin options page.



Also, the refactor command can be configured with a key shortcut



Information about the plugin and download link is placed in the CR_ExtractHqlNamedQuery wiki page on the DXCore Community Plugins project.

Special thanks to Rory Becker for all the information he has shared about DXCore plugin development

21 de marzo de 2010

Detectando errores de bindeo de assemblies

A veces, cuando la aplicación que estamos desarrollando esta dividida en muchos assemblies, detectar un problema de referencias (ya sea un problema de versionado o directamente la falta de un determinado assembly) puede resultar bastante difícil.

El error que el runtime muestra es algo parecido a lo siguiente:

System.IO.FileLoadException: Could not load file or assembly 'B, Version=1.1.0.0, Culture=neutral, PublicKeyToken=1735b5aafe024a29' or one of its dependencies.
The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)
File name: 'B, Version=1.1.0.0, Culture=neutral, PublicKeyToken=1735b5aafe024a29'
at A.Class1..ctor()
at ConsoleApplication1.Program.Main(String[] args) in C:\VSProjects\AssemblyBindingTest\ConsoleApplication1\Program.cs:line 17

WRN: Assembly binding logging is turned OFF.
To enable assembly bind failure logging, set the registry value [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD) to 1.
Note: There is some performance penalty associated with assembly bind failure logging.
To turn this feature off, remove the registry value [HKLM\Software\Microsoft\Fusion!EnableLog].

En este ejemplo, una aplicación de consola referencia a un assembly A.dll, y este assembly a su vez referencia a otro assembly B.dll
La versión correcta de B.dll debería ser la 1.1.0.0, pero en la carpeta donde se ejecuta la aplicación, deliberadamente hice deploy de la versión 2.1.0.0
Lo único que este mensaje de error me dice, es que no se pudo levantar el assembly 'B, Version=1.1.0.0, Culture=neutral, PublicKeyToken=1735b5aafe024a29', o alguna de sus dependencias.

Lo cual no me dice mucho para resolver mi problema, en especial si esto ocurre en un ambiente con decenas de assemblies e inclusive algunos quizás no desarrollados por mi, sino por terceros.

Lo importante del mensaje es lo siguiente:
WRN: Assembly binding logging is turned OFF.
To enable assembly bind failure logging, set the registry value [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD) to 1.

Aquí me dice que asignando el valor 1 a la entrada del registro HKLM\Software\Microsoft\Fusion!EnableLog se puede habilitar el "Assembly binding logging", vamos a hacerlo y veamos como cambia el mensaje de error que nos muestra el runtime:

System.IO.FileLoadException: Could not load file or assembly 'B, Version=1.1.0.0, Culture=neutral, PublicKeyToken=1735b5aafe024a29' or one of its dependencies.
The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)
File name: 'B, Version=1.1.0.0, Culture=neutral, PublicKeyToken=1735b5aafe024a29'
at A.Class1..ctor()
at ConsoleApplication1.Program.Main(String[] args) in C:\VSProjects\AssemblyBindingTest\ConsoleApplication1\Program.cs:line 17

=== Pre-bind state information ===
LOG: User = WinXP\Jorge
LOG: DisplayName = B, Version=1.1.0.0, Culture=neutral, PublicKeyToken=1735b5aafe024a29
(Fully-specified)
LOG: Appbase = file:///C:/VSProjects/AssemblyBindingTest/ConsoleApplication1/bin/Debug/
LOG: Initial PrivatePath = NULL
Calling assembly : A, Version=1.1.0.0, Culture=neutral, PublicKeyToken=10f50b7c12e8c3d1.
===
LOG: This bind starts in default load context.
LOG: No application configuration file found.
LOG: Using machine configuration file from c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\config\machine.config.
LOG: Post-policy reference: B, Version=1.1.0.0, Culture=neutral, PublicKeyToken=1735b5aafe024a29
LOG: Attempting download of new URL file:///C:/VSProjects/AssemblyBindingTest/ConsoleApplication1/bin/Debug/B.DLL.
WRN: Comparing the assembly name resulted in the mismatch: Major Version
ERR: Failed to complete setup of assembly (hr = 0x80131040). Probing terminated.

La primer parte del mensaje es igual a lo que obteníamos antes, pero ahora nos indica además el assembly "llamador":
Calling assembly : A, Version=1.1.0.0, Culture=neutral, PublicKeyToken=10f50b7c12e8c3d1.

Y lo mas importante, el motivo por el cual no se pudo realizar el bindeo.
LOG: Attempting download of new URL file:///C:/VSProjects/AssemblyBindingTest/ConsoleApplication1/bin/Debug/B.DLL.
WRN: Comparing the assembly name resulted in the mismatch: Major Version

Con esto sabemos que el assembly que no se pudo bindear es B.dll (y no alguna de sus dependencias, como sugería el mensaje original) y específicamente sabemos que el problema es de versionado (la dll que el runtime encontró es de una versión mayor a la requerida por la aplicación)

Por último, no olvidemos lo que nos decia el mensaje a modo de advertencia sobre habilitar Assembly Binding Logging:
Note: There is some performance penalty associated with assembly bind failure logging.
To turn this feature off, remove the registry value [HKLM\Software\Microsoft\Fusion!EnableLog].

Así que para evitar problemas de performance, luego de haber descubierto y solucionado nuestro problema, deshabilitamos el assembly binding logging asignando un 0 a HKLM\Software\Microsoft\Fusion!EnableLog

Espero que sea útil, cualquier comentario será bienvenido !

18 de marzo de 2010

Asociar un schema (xsd) a los archivos de configuracion de NHibernate y Spring.net en Visual Studio

Este es el primer post de muchos otros que estarán por venir... o al menos eso espero ;)

Para romper el hielo, voy a empezar con algo sencillo, pero que puede llegar a resultar bastante útil.

En Visual Studio, si uno quiere que un determinado archivo xml se valide contra un schema y además tener disponible la funcionalidad de Intellisense, es necesario asociar el xml al xsd en cuestión. Esto se puede hacer fácilmente mediante el menú Xml\Schemas, luego se selecciona el xsd de la lista o se busca un archivo xsd en disco. Hasta ahí todo bien, el problema es que es necesario realizar esta asociación en cada xml, uno por uno, y aun peor es el hecho de que esas asociaciones quedan registradas en el archivo de opciones de usuario de la solución (.suo), así que si me llevo un proyecto a otra solución estaría perdiendo todas las asociaciones entre xml y xsd que tenia dentro del proyecto.

Este problema se puede evitar siguiendo los siguientes pasos (voy a ejemplificar usando archivos xsd de NHibernate y Spring.net, pero la solución aplica para otros casos)

Buscar la carpeta Xml\Schemas, dentro de la ruta donde fue instalado el Visual Studio, y copiar los archivos xsd que queremos asociar a nuestros xmls (por ejemplo "C:\Program files\Microsoft Visual Studio 9.0\Xml\Schemas\").

En este caso, copiamos los archivos xsd para configuración de Spring.net y los archivos xsd de configuración y de mapping de NHibernate:

spring-objects-1.3.xsd
nhibernate-configuration.xsd
nhibernate-mapping.xsd

Luego buscar el archivo Catalog.xml que, siguiendo el ejemplo anterior, debería encontrarse en "C:\Program files\Microsoft Visual Studio 9.0\Xml\Schemas\Catalog.xml" y agregar estas tres lineas dentro del tag SchemaCatalog.


<SchemaCatalog xmlns="http://schemas.microsoft.com/xsd/catalog">
...
<Schema href="%InstallRoot%/xml/schemas/nhibernate-mapping.xsd"
targetNamespace="urn:nhibernate-mapping-2.2"/>
<Schema href="%InstallRoot%/xml/schemas/nhibernate-configuration.xsd"
targetNamespace="urn:nhibernate-configuration-2.2"/>
<Schema href="%InstallRoot%/xml/schemas/spring-objects-1.3.xsd"
targetNamespace="http://www.springframework.net"/>
...
</SchemaCatalog>


Una vez hecho esto, cuando abramos un archivo xml que tenga asociado alguno de los namespaces identificados en el atributo targetNamespace, automáticamente Visual Studio nos va a asociar el schema xsd que corresponda.

Pongo como ejemplo un archivo de configuración de Spring.net, donde el namespace se asigna mediante el atributo xmlns:


<objects xmlns="http://www.springframework.net">
<object name="MyMovieLister"
type="Spring.Examples.MovieFinder.MovieLister, Spring.Examples.MovieFinder">
</object>
</objects>


Espero que sea útil, cualquier comentario será bienvenido !