martes, octubre 26, 2010
Todo hombre de negocios sabe que no debe regalar aquello que puede vender. Todo hombre de negocios inteligente sabe, además, que no debe tirar aquello que puede regalar.
"La Cara Oculta de Delphi 6" es un libro al que profeso mucho cariño. Invertí en su preparación muchas horas, me sirvió para afianzar mi empresa... y estoy satisfecho con el resultado. Pero ya ha pasado tiempo suficiente como para dar por cerrada una etapa profesional, y para pasar a la siguiente. Aquí está la versión electrónica del libro, por si a alguien todavía le interesa:
Es tiempo de ascender otro escalón...
miércoles, septiembre 08, 2010
Cómo espiar los mensajes de un control
Sigo con trucos necesarios para implementar un sistema de gestión de ventanas más o menos complejo. Imagine que tiene un botón, y que debe responder a los clics del mismo. Ni lo piense: desde la ventana, o desde la clase que contiene al botón, añade un manejador al evento Click del botón. Esta es una ventaja que tiene .NET y su sistema de eventos multicast sobre el viejo y más sencillo sistema de eventos de Delphi nativo. En Delphi nativo solamente podíamos asociar un único manejador a cada evento. En .NET, el evento puede disparar cuantos manejadores necesitemos.
La gran pregunta: ¿y qué ocurre si lo que quiero interceptar es un mensaje que no tiene un evento de "alto nivel" asociado? La solución "clásica" es crear una clase derivada a partir del botón. Pero, ¿y si no es posible hacerlo? Por ejemplo, puede que le toque lidiar con un botón que usted no ha creado (la culpa siempre es de otro). En mi caso, no se trata de un mero botón, sino de la mismísima ventana principal. Estoy escribiendo un gestor genérico de ventanas, y necesito interceptar el mensaje WM_ACTIVATEAPP. Podría obligar al programador que utilice mi gestor a derivar su ventana principal de una clase base mía, debidamente retocada. Sería chapucero, no obstante.
Este es un problema bastante general del modelo de programación orientada a objetos que terminó imponiéndose en nuestros lenguajes de programación: el tipo de objeto va unido indisolublemente a su entidad. Un coche de turismo no se puede convertir dinámicamente en una ambulancia... aunque en la vida real, los turismos puedan utilizarse así. Este problema hace complicado trabajar con bases de datos orientadas a objetos cuyo modelo de objetos sea parecido al de C++, Java o C#. No me malinterprete: el modelo con tipos estáticos es estupendo para escribir cierto tipo de programas, pero es malo para las bases de datos, en las que un objeto de la clase Contacto puede fácilmente en un Cliente, y no podemos perder las referencias al contacto durante la metamorfosis.
Lo interesante es que existe una solución muy conocida para este problema... en el API de bajo nivel de Windows; si busca window subclassing encontrará montones de páginas sobre esta técnica. La pregunta es, ¿cómo conseguir lo mismo utilizando Windows Forms?
La respuesta la tiene la clase NativeWindow, que nos evita tener que usar PInvoke o cosas peores. ¿Hay que interceptar los mensajes que recibe un control? Pues creamos una clase derivada de NativeWindow, sobrescribimos su método virtual WndProc y, para asociarla al puñetero botón, usamos el método AssignHandle de NativeWindow para copiar en una instancia de la nueva clase el identificador de ventana del botón a vigilar. Los mensajes de ventana que normalmente recibiría el botón serán ahora recibidos también por nuestra instancia de la clase derivada de NativeWindow.
Es cierto que seguimos necesitando crear una clase por herencia. Si necesitamos realizar esta operación de vigilancia con N tipos diferentes de botones, ¿significa que tendremos que crear N clases derivadas de NativeWindow? Resulta que no: podemos definir una sola clase derivada de NativeWindow sin tener conocimiento del control concreto que vigilaremos y las acciones concretas que queremos ejecutar. Lo que haríamos en su WndProc, sencillamente, sería llamar a una variable de tipo delegate que especificaríamos al crear las instancia de dicha clase. Y sería ese delegado donde implementaríamos el código necesario:
private sealed class PeepingTom : NativeWindow, IDisposable
{
public delegate void WindowProc(ref Message m);
private WindowProc tommy;
public PeepingTom(Control ladyGodiva, WindowProc tommy)
{
this.tommy = tommy;
this.AssignHandle(ladyGodiva.Handle);
}
public void Dispose()
{
this.ReleaseHandle();
}
protected override void WndProc(ref Message m)
{
tommy(ref m);
base.WndProc(ref m);
}
}
Los nombres, por supuesto, hacen referencia a la historia de Lady Godiva, que se paseó desnuda a caballo por todo Coventry para obligar a su marido a bajar los impuestos (¡que no se le ocurra la idea a la Sonsoles, please!), y a un tal Peeping Tom, o Tomasito el Mirón, que desobedeciendo las órdenes del cabreado marido, se escondió para regocijarse con la visión de la damita en pelotas.
Suponga ahora que tenemos un formulario con un botón, y que queremos saber cuándo el ratón pasa por encima del botón. Para asignarle un espía al botón, dado el diseño de nuestra clase, tenemos que esperar al evento Load del formulario, para asegurarnos de que el identificador de ventana del botón haya sido creado. En ese momento, hacemos algo parecido a esto:
private void Form1_Load(object sender, EventArgs e)
{
new PeepingTom(button1,
delegate(ref Message m)
{
if (m.Msg == 0x02A1)
this.Text = "Mouse hover";
else if (m.Msg == 0x02A3)
this.Text = "Mouse leave";
});
}
El ejemplo no es muy bueno, porque para esta tarea en particular, ya tenemos eventos en la clase Button, pero espero que sirva para que se haga una idea.
Etiquetas: .NET, controles, WinAPI, Windows Forms
sábado, septiembre 04, 2010
Una ventana dentro de otra
Este es un truco rápido y sencillo que, sin embargo, es difícil encontrar a la primera buscando en Internet:
- ¿Cómo meter una ventana dentro de otra?
¿Y para qué le puede hacer falta tal cosa? Un uso evidente sería crear un sistema de docking, que permita agrupar dinámicamente varias ventanas dentro de un contenedor; naturalmente, saber cómo meter una ventana independiente dentro de otro control es sólo el comienzo del proceso.
Pero existe otro uso más mundano: imagine que está creando un administrador de ventanas. Teóricamente, el administrador debe permitirle cambiar el estilo de la aplicación a petición del usuario: de ventanas SDI individuales, a una interfaz basada en pestañas e incluso a un sistema MDI. ¿Cómo deberían implementarse las ventanas para que pudiesen utilizarse con este gestor? Una posibilidad, que he utilizado en varios proyectos, es que las "ventanas" se diseñen como controles de usuarios (UserControl). El motivo es lo fácil que se añade un control dentro de otro, ya sea en tiempo de diseño o de ejecución.
Sin embargo, no es tan evidente cómo meter una instancia de la clase Form dentro de, digamos, un TabPage. Si lo intenta, sin más, provocará una excepción advirtiendo que "no se puede meter un control de nivel superior dentro de otro". El error, por fortuna, nos indica también la solución. Hay una propiedad poco conocida de la clase Form, que hay que poner a false. Por ejemplo, si queremos meter una instancia de la clase Form2 dentro de una nueva página de un TabControl, necesitaremos algo parecido a esto:
var f2 = new Form2();
f2.TopLevel = false;
f2.FormBorderStyle = FormBorderStyle.None;
f2.Dock = DockStyle.Fill;
var tp = new TabPage(
tabControl1.TabPages.Count.ToString());
tabControl1.TabPages.Add(tp);
tp.Controls.Add(f2);
f2.Visible = true;
Aquí he metido la ventana, que puede haber sido diseñada al antojo del programador, dentro de un control de pestañas. Pero es igualmente posible meterla directamente dentro de un prototipo vacío de ventana hija MDI... o lo que se le ocurra.
Etiquetas: .NET, controles, Windows Forms
domingo, agosto 22, 2010
Validación en Entity Framework
Metodologías e ideologías
Debo comenzar confesando que la moda domain-driven me parece ridícula. Es cierto que la programación orientada a objetos es estupenda… y que el modelo relacional también lo es. También es cierto que, al mezclarlos, se produce un desajuste que en inglés llaman impedance mismatch. Pero los intentos de "resolver" el problema mediante la metodología domain-driven consisten, básicamente, en cargarse todas las bondades del modelo relacional, de la arquitectura cliente-servidor y, de paso, la mayoría de los avances aparecidos y probados en estos cuarenta años de bases de datos relacionales.
No es éste el lugar para explicar todo lo que tengo en contra del DD, por lo que me limitaré a un solo argumento. ¿Existe alguna diferencia básica, digamos, entre una aplicación "de negocios" y una aplicación, digamos, como Word o un sistema operativo? Claro que la hay: el dinamismo de las llamadas reglas de negocio. Usted puede escribir una clase para un procesador de texto y atiborrarla de reglas sobre el tratamiento de las mayúsculas. Probablemente, dentro de cinco años esas reglas seguirán siendo igual de malas o apropiadas. En cambio, las reglas para el funcionamiento de una aplicación de negocios cambian constantemente. Si usted enchufa esas reglas como parte de métodos de las clases "de negocio", tendrá que contratar a un nerd granujiento a jornada completa, para que cada vez que el dueño del negocio se cambie los gayumbos, cambie la programación de las clases afectadas, recompile y reinstale el sistema.
Por supuesto, esto es lo que todo nerd desea: un trabajo fijo a jornada completa, ya sea cambiando detallitos insignificantes de la clase Order o "tuneando" el núcleo de su instalación personal de Linux, que le deje tiempo para conectarse al Facebook y para jugar a dragones y mazmorras. Es comprensible. Otra cosa es que sea racional o eficiente. O bueno para alguien excepto para el propio nerd.
La cabra y el monte
Las modas, al final, visten a personas reales. Quiero decir, que si al artista se le olvidó la ranura en el pantalón para hacer pis, el portador de la prenda ya se las arreglará para orinar; eso, o reventará. Por mucho domain-driven que haya infectado la cabecita del pobre programador, en cuanto necesite hierba, la cabra irá al monte… o reventará, ya sabe. ¿Un ejemplo? Teóricamente, en el mundo ideal de nuestros guitar heroes con acné, cada clase debería ocuparse de su propia validación. Pero, ¡oh, qué penita!, los programadores reales que trabajan con Entity Framework han adquirido la costumbre de acumular validaciones dentro del código del método SaveChanges (hay varias formas de conseguirlo).
¿Es eso bueno, o malo? Hay de todo. Esperar a SaveChanges para comprobar que una fecha de nacimiento no pertenece al siglo XII, por ejemplo, es una soberana estupidez. Pero si la regla depende del estado de una entidad, no le queda más remedio que enredar con el administrador del estado: la pureza domain-driven exige que este estado sea externo a la entidad. Por ese motivo, era tan complicado utilizar entidades con servicios WCF en la versión anterior de .NET. Y por ese motivo, gracias al cielo, Microsoft ha terminado ciscándose en la pureza y regalándonos self-tracking entities en la nueva versión. Viva lo impuro cuando resuelve problemas.
¿Y qué dice la sacrosanta teoría sobre todo esto? En honor a la verdad, la Programación Orientada a Objetos dice que, en este caso particular, la centralización es mala. Y tiene razón… si se tiene en cuenta el contexto sobre el que se desarrolla esta metodología: un mundo en el que las principales extensiones a una aplicación se producen principalmente mediante la incorporación de nuevas entidades. Pero este no es el escenario característico de una aplicación de ventas, de préstamos o de Bolsa, donde es más probable que aparezcan nuevas reglas para los viejos objetos que nuevos objetos con nuevas reglas. El impacto de la modificación de las nuevas reglas es mayor si el código que las controla está disperso a lo largo y ancho de los ficheros individuales de clases de entidades.
Y hay más: observe que estas reglas de negocio suelen referirse al estado observable de las entidades de negocio. Traducido a código fuente: las reglas de negocio, por lo general, afectan a propiedades públicas de estas clases. Al fin y al cabo, estas reglas son establecidas por analistas funcionales… o por el propio jefe del cotarro; es decir, por gente que no tiene por qué enredarse con protected, private e internal.
Cabalgando la ola
La centralización, por lo tanto, no siempre es mala; digamos, entonces, que aceptamos que una parte de las reglas de negocio se implementan como parte del mecanismo de sincronización de los cambios con la base de datos. Sin embargo, ¡seguimos necesitando al nerd! Para cada cambio en las reglas, sigue siendo necesario recompilar la aplicación… y redistribuirla.
No sólo eso: imagine que su aplicación la utilizan varias empresas. O incluso, que la utiliza una sola empresa, pero que ésta tiene sucursales en distintos países, con diferentes legislaciones e idiosincrasias. Cada vez que en China cambien las reglas de descuentos para sujetadores, en la sucursal de Groenlandia tendrán que reinstalar el maldito software de ventas.
¿Y si movemos esas reglas fuera de la aplicación? Hablo de ubicar las reglas en un fichero de texto, de manera que la aplicación lo lea, digamos que al iniciarse. El contenido del fichero sería traducido a código ejecutable (eso es muy sencillo, con toda la tecnología que ofrece .NET) que se ejecutaría como parte del método SaveChanges del contexto.
Será más sencillo si muestro un ejemplo de reglas. La sintaxis concreta que he utilizado imita a la de C#, pero está claro que las variaciones son posibles:
class Order
{
on insert
Created = DateTime.Now;
on update
Modified = DateTime.Now;
rule MustPaidBeforeShipping is
Paid || !ShipDate.HasValue;
}
La explicación:
- Las reglas se definen por clase. Esto es así por la forma en la que puede tener lugar la validación durante OnSavingChanges: el componente que ejecuta el script recorrerá los ObjectStateEntry modificados en el contexto. Para cada uno de ellos, se elegirá el conjunto de reglas a ejecutar o verificar de acuerdo al tipo de entidad asociado, y luego, al estado de la entidad.
- Es recomendable que se respete la herencia. Si una regla se define en una clase base, debe respetarse también en las posibles clases derivadas, siempre que una de estas no la anule.
- Como mínimo, se deben soportar dos tipos de reglas: verificaciones y asignaciones a propiedades. En el ejemplo, hay dos asignaciones a propiedades, separadas según el estado de la entidad, y una sola verificación, que al no mencionar el estado, se activará tanto para inserciones como para actualizaciones.
- Cada regla tiene un identificador asociado. El escenario típico de estas validaciones será un proceso WCF remoto. Imagine que asociamos un mensaje de error directamente con la regla: ¿en qué idioma debería estar? Un servicio de capa intermedia debe evitar atarse a un idioma concreto, porque puede estar siendo utilizado por clientes configurados para idiomas diferentes. De todos modos, debería ser posible sustituir el identificador por un literal de cadena, para casos sencillos en que no se prevé la traducción a otros idiomas.
- Las expresiones trabajarían, principalmente, sobre entidades aisladas, por lo que no habría que preocuparse por extensiones del tipo LINQ: si usted quiere verificar que el total de una orden no sea superior a un valor, por ejemplo, prográmelo donde realmente corresponde, es decir, en la base de datos. Entity Framework no le puede ofrecer garantías, llegados a este punto, de que el grafo de entidades asociados a la entidad modificada, esté completamente cargado en memoria. Sólo piense, por ejemplo, lo difícil que es verificar la unicidad de una columna en un proceso cliente, sin tener todas las entidades en memoria y sin lanzar una consulta adicional al servidor.
Sobre las expresiones soportadas: en principio, no haría falta complicarlas con índices y corchetes, por las razones antes expuestas. Las expresiones básicas serían constantes y "rutas de objetos" (object paths). Estas rutas siempre comenzarían por una propiedad de la clase que define la regla, o por una clase o espacio de nombres. En nuestro ejemplo, ShipDate.HasValue comienza por una propiedad de la clase Order, pero DateTime.Now es, evidentemente, una propiedad estática de una conocida clase. Este tipo de sintaxis es muy fácil de implementar y traducir.
Las expresiones, que son el núcleo de nuestro sistema, se traducirían como árboles de expresiones LINQ, que se pueden compilar a código nativo dinámicamente. En esto ya tengo también bastante experiencia: el sistema es sencillo y muy eficiente (lo he utilizado en filtros a la medida para un servidor RTD para Excel, implementado dentro de una aplicación de servicios de Windows).
En resumen
Se trata solamente, en esta etapa, de una propuesta: crear un componente que, a partir de un script externo, se ocupe de determinadas validaciones e inicializaciones dentro del Entity Framework. Es fácil señalar el método SaveChanges como el punto donde se insertaría el componente de validación. Probablemente existan más puntos donde se pueda actuar.
En principio, el script con las reglas puede estar ubicado en un fichero de texto. Esto sería apropiado si las aplicaciones clientes accediesen a las entidades a través de un servicio WCF. Sería este servicio quien centralizaría el manejo del script y determinaría las reglas a cumplir. Pero sería sencillo extender el mecanismo, en ausencia de una capa intermedia, ubicando las reglas en la base de datos, para simplificar el cambio de las mismas.
Por supuesto, escribo esto, en este momento, para recabar sugerencias.
Etiquetas: bases de datos, EF, ideas
miércoles, junio 02, 2010
lunes, marzo 29, 2010
Trabajo en Delphi
Se busca programador en lenguaje Delphi, abstenerse freelance, se valoraran conocimientos en base de datos SQL. Imprescindible Delphi.
Empresa situada en Madrid. Enviar curriculum a wis@worldwis.es.
Empresa situada en Madrid. Enviar curriculum a wis@worldwis.es.
(... y sí: dentro de poco habrá noticias sobre nuevos cursos de C#, y gracias a Marcos Santín, de Delphi, y más...)
viernes, julio 24, 2009
Cagarse en el chocolate
Serie: Los N Pecados Capitales
En la empresa de Willy Wonka, se fabrica un exquisito chocolate. Al menos, esa es la opinión general. La producción está a cargo de los umpalumpas: una extraña raza de friquis canijos, adictos a la programación en Java, a los que pagan con granos de cacao.
Los umpalumpas tienen la curiosa manía de inventarse canciones sobre la marcha, y luego pretender que se trata de tonadas tradicionales, de las de toda la vida. Por ejemplo, si un niño glotón se cae dentro del río de chocolate, improvisan una canción explicando por qué no se debe uno inclinar demasiado sobre la corriente. Si un proyecto de desarrollo fracasa estrepitosamente, se inventan melodías que hacen referencia a dominios escuálidos, programación ágil e inversión del control. Son pintorescos, estos personajillos.
Pero los umpalumpas tienen también una costumbre muy fea. Alex, un umpalumpa de Pontevedra, revuelve el chocolate fundido cuando siente que algo más se revuelve en su interior:
- Willy… ehm… señor Wonka.
- ¿Sí, Alex?
- Tengo un apretonciño de intestinos…
- Te aguantas. Esta barra de chocolate tenía que estar para ayer.
- ¡Pero es que me lo voy a hacer encima!
- Caga entonces dentro de la tina del chocolate.
- ¡¿Cómo?!
- Joder, Alejandrito, ¿tengo que dibujarte un diagrama de clases?
- Pero, ¿cómo vamos a echar mierda dentro del chocolate?
- A ver, ¿de qué color es el chocolate? Marrón. ¿Y la mierda? Marrón. Un poco de mierda espesa aporta crema al cacao.
- Pero… es que estoy un poco estreñido y estoy cagando boliñas, como las de las cabras…
- Pues mejor aún: chocolate con cacahuetes, y si las bolas son más grandes, con nueces y avellanas.
- ¿Sí, Alex?
- Tengo un apretonciño de intestinos…
- Te aguantas. Esta barra de chocolate tenía que estar para ayer.
- ¡Pero es que me lo voy a hacer encima!
- Caga entonces dentro de la tina del chocolate.
- ¡¿Cómo?!
- Joder, Alejandrito, ¿tengo que dibujarte un diagrama de clases?
- Pero, ¿cómo vamos a echar mierda dentro del chocolate?
- A ver, ¿de qué color es el chocolate? Marrón. ¿Y la mierda? Marrón. Un poco de mierda espesa aporta crema al cacao.
- Pero… es que estoy un poco estreñido y estoy cagando boliñas, como las de las cabras…
- Pues mejor aún: chocolate con cacahuetes, y si las bolas son más grandes, con nueces y avellanas.
Por desgracia, la mierda de umpalumpa es sumamente corrosiva. Por su culpa, caen aviones, revientan naves espaciales, fallan sistemas de soporte vital en hospitales y se aplican dosis excesivas de radiación a pacientes de radioterapia. Será mierda, pero hay que tomársela muy en serio.
Hay mil y una maneras de cagarse en el chocolate. Por mencionar unas pocas:
- Una aplicación para almacenar expedientes médicos, escrita en dBase, generaba claves primarias mediante el sucio mecanismo de contar los registros en el fichero y aumentar el valor en uno. Mientras el hospital tuvo un único ordenador en admisión, el chocolate sabía a ambrosía. Un día instalaron un segundo ordenador, y en algún momento, se crearon dos registros simultáneamente… con la misma clave primaria. Migrando la aplicación a InterBase, en el 2000, alguien tropezó con la historia clínica de un buen señor que había sufrido la extirpación de sus ovarios.
- En la historia anterior, el error no se detectó porque los índices de aquella versión de dBase no permitían la comprobación de unicidad. Pero conozco un par de sistema implementados sobre Oracle en los que los “diseñadores” (es un decir) han evitado el uso de claves primarias concienzudamente.
- La mismísima Borland se cagó, en su momento, en el chocolate de la casa. Cuando apareció Delphi 2, se produjo una ola de fallos en aplicaciones de servidor. La causa: en servidores con procesadores Xeon, fallaban determinadas operaciones sobre cadenas de caracteres, porque la implementación del tipo String no era thread-safe.
- Todos sabemos, o eso espero, que no debemos acceder a los métodos de un objeto de ventana desde un hilo diferente a aquel en el que fue creada. Sin embargo, este tipo de violación no es verificada en ningún caso por .NET Framework v1.1. Muchas aplicaciones, por lo tanto, ignoran total o parcialmente el problema… hasta que migran a una versión más reciente de la plataforma y empiezan a reventar por todas partes. Entonces, improvisan cantando los umpalumpas, la culpa es de Microsoft, evidentemente.
¿Se ha dado cuenta ya de cuál es el patrón emergente? Se trata siempre de fallos que se producen al no tener en cuenta la concurrencia. La técnica ingenua de generación de claves primarias funciona mientras hay un solo ordenador. La implementación defectuosa de las operaciones de cadenas funciona incluso cuando hay varios hilos, pero siempre que exista un único núcleo. Los errores de reentrancia de Windows Forms y WPF se producen pero no se notan… hasta que el sistema revienta por causas desconocidas.
Cuando se trabaja con una base de datos relacional, además, en teoría existe una forma muy sencilla de evitar la mayoría de los errores: trabaje con transacciones serializables. ¡Pero la mayoría de los programadores, o desconocen su existencia, o tienen miedo a la pérdida de velocidad! De hecho, la mayoría de las operaciones complejas en las bases de datos habituales pueden implementarse con un nivel de aislamiento más bajo, si se ordenan inteligentemente las instrucciones. Esto es algo, sin embargo, que se encuentra fuera del alcance de la mayoría. Y quien sospecha que hay algo que puede fallar en su implementación, termina cagando dentro del chocolate: ya haremos algo cuando falle.
Otra fuente habitual de tropezones en el cacao: los errores de redondeo. ¿Cuántos programadores, de los que trabajan con aplicaciones financieras, tienen idea sobre los problemas de estabilidad numérica y sus consecuencias en el desarrollo? Es más fácil tener una idea vaga de que existe un problema "teórico", y dejar que sea el desdichado usuario el que hunda sus dientes en la mierda de umpalumpa. Sólo entonces merece la pena hacer algo.
¿Existe alguna solución mágica para evitar sustancias extrañas en nuestros alimentos? Por desgracia, sólo hay una forma, y requiere una cualidad personal muy extraña y poco valorada: la ética profesional. Pero estamos en crisis, y la ética es un bien caro y escaso. Al menos, deberíamos presionar a las autoridades europeas para que los productos de software lleven una etiqueta de advertencia, como las de los sándwiches que venden en las gasolineras:
"Puede contener trazas de cacahuetes, pescado, huevo, gluten
y mierda de umpalumpa."
y mierda de umpalumpa."
Etiquetas: disparates, metodología