sábado, noviembre 29, 2008

Consenting adults, consenting ducks

No le toques los colmillos al Conde DuckulaHace poco, escribía en los comentarios de otra entrada que esto la Informática no es una ciencia, sino una ingeniería... y Alfredo Novoa respondía que ni siquiera eso. La siguiente anécdota demuestra cuán cierta es, desgraciadamente, esta afirmación.
La historia viene a cuento de una característica "novedosa" llamada duck typing. Según sus defensores, si una clase tiene métodos Add, Remove e Insert, y un indexador, ¿qué hay de malo en asignar una instancia de la clase a una variable de tipo ICollection? Eso es, en pocas palabras, el duck typing: si algo camina como un pato, nada como un pato, vuela como un pato (es decir, las tres cosas las hace mal) y además caga como un pato (es decir, constantemente), entonces, según los adeptos del mecanismo, tiene que ser un pato. Naturalmente, yo digo que puede ser una oca, o un pato robot, o el conde Duckula. No es buena idea tratar al señor conde como a un vulgar pato. Y así lo hice ver aquí, aunque de pasada:
Algún tiempo más tarde, alguien se quejó en un comentario... y como no suelo llevar cuenta de los comentarios tras cierto tiempo, no me percaté del hecho hasta hace un par de días. Esta es el comentario:
Cuál es el problema que le ves al "duck typing". Los que lo utilizan, suponen que los programadores son "consenting adults".
Consenting adults, eh...
En principio, eso serviría también para justificar las conversiones de tipos bestiales que son el pan de cada día en C++. Al fin y al cabo, si yo sé que un puntero se representa como un entero de 32 bits en mi plataforma, ¿por qué no voy a poder incrementar ese entero en cuatro para acceder al siguiente elemento de un vector? Consenting adults. No estamos locos. Sabemos lo que queremos...
Ahora bien, ¿es tan peligroso el duck typing como las conversiones bestiales? Veo dos problemas principales:
  1. Una pila no es simplemente un objeto que tiene dos métodos Push y Pop. Esos dos métodos, además, tienen que satisfacer determinado contrato. En la práctica, si el pato es mío, es un descuido mío el que no haya hecho que la clase correspondiente implemente la interfaz deseada. Si el pato es ajeno, es muy peligroso asumir que puedo tratar a un águila como a un pato simplemente porque ambos tienen pico.
  2. Si el pato es ajeno, además, los problemas con el versionado de clases pueden ser peliagudos. O "plumiagudos", si lo prefiere...
En cualquier caso, me hace gracia el uso de la teoría de los consenting adults. Hace poco, en Alemania, un buen señor pidió a otro consenting adult que le cortase la picha y se la comiese (en ese orden). El otro, efectivamente, cumplió con lo pactado. Luego mató a la víctima, como ésta había solicitado, se comió un muslo, y puso el otro a secarse para hacer jamón. Al caníbal lo pillaron, y lo llevaron a juicio. E inmediatamente, se armó el correspondiente revuelo: ¿por qué condenarlo? ¿No se limitó a hacer lo que el otro pirado le pedía? ¿No eran una parejita de consenting adults?
Evidentemente, tengo mi propia opinión al respecto. Yo, al canibalizado, si lo pillase antes de ser devorado, no le haría nada. Al fin y al cabo, si quiere que otro lo mate y se lo zampe, es su problema. Es, en efecto, un consenting adult: se supone que sabe lo que hace. Mi problema es con el caníbal: es un pirado, y tengo serias sospechas de que intentará repetir la acción. La próxima vez, no estoy seguro de que vaya a encontrar un consenting adult para satisfacer sus antojos culinarios. Es un individuo peligroso. No sé si eso bastará (junto con lo que ya ha hecho) para enviarlo a la cárcel, pero en mi barrio no lo quiero.
Lo mismo se aplica a los duck typers. ¿De manera que usted es aficionado a transformar el agua en vino, perros en patos y patos en borricos? Ah, claro, usted sabe lo que hace. Pero lo que usted hace es peligroso. No me gustaría verle enredando con mis proyectos...

Etiquetas: ,

viernes, noviembre 28, 2008

¡No publique vectores!

martes, noviembre 18, 2008

¡Te lo dije!

They never scored!Permítame que me eche unas risas... ya, sí, ya puedo continuar...
Sin duda, usted conocerá a Beavis y su amiguete Butt-head. Son dos friquis metaleros de dibujos animados, que se pasaban la vida intentando "anotar"; meter un gol, ya me entiende... Cuando la serie llegó a su fin, el creador, jocosamente, escribió un epitafio para sus personajes:
- They never scored.
Nunca "anotaron". Pobrecillos. Lo mismo, por desgracia, se puede decir del compilador de Borland para .NET: nunca llegó a marcar gol. Murió virgen aunque no casto.
¿Una tragedia? Nah. Tragedias son las guerras, el hambre, las enfermedades. Concedo que puede ser una molestia. Mi sabor favorito de Häagen-Dazs ya no se vende en mi barrio. Es molesto, e irritante, eso sí. De todos modos, tengo todo el derecho del mundo a decir: ¡TE LO DIJE!

... y nada más que la verdad

Veamos los hechos desnudos: un buen día, la compañía antes conocida como Inprise decide que su negocio de compiladores es una ruina. Segrega la compañía (igual que las babosas segregan un moquillo adherente), bautiza CodeGear al retoño, y anuncia que tiene un montón de novios. Pero la petición de mano se demora infinitamente y toda la villa comienza a sospechar que se trataba de un farol. Tras varias peripecias, aparece el Príncipe Azul, un tal Embarcadero, cabalgando radiante sobre su moto acuática, y carga con la solterona (¿le habrán pagado para que se la llevase de una puñetera vez?).
Uno esperaría que, tras tan accidentado romance, la parejita se pusiese a fabricar pequeños ogros, como Fiona y Shrek, pero... es que Fiona ya aportaba al enlace dos monstruitos: Delphi para Win32 y Delphi.NET. Ah, sí, y esa abominación de "Delphi para PHP" y otros engendros que mejor dejo sin clasificar. Bueno, menos trabajo para el ogro, ¿o no?
Entonces, la comedia se transforma en drama: Shrek tira Delphi.NET por la ventana y toma en adopción el brillante hijo de un vecino, llamado Oxygen, aka Chrome. ¿Sabía Shrek lo que iba a hacer con su vástago desde el primer momento? ¿Se convenció a su pesar de que Delphi.NET no tenía arreglo? A todas estas, los representantes de Fiona parecen haberse puesto de acuerdo en mostrar su mejor sonrisa a los medios. Pero uno imagina que la procesión debe ir por dentro...

El bien o el mal

No me entienda mal: Oxygen es un producto que me gusta y que recomiendo sin reservas. De hecho, en cierta manera lo considero un familiar cercano. Freya ha copiado impúdicamente algunas ideas de Oxygen/Chrome, como la palabra clave method o los niveles de visibilidad. Muchas características comunes son fruto de una evolución paralela: es un indicio de que ambos proyectos arribaron a los conclusiones lógicas en esos puntos. A la vez, me empeño en creer voluntariosamente que puede que alguna característica de Freya se haya podido filtrar a Oxygen/Chrome.
¿Es el párrafo anterior una recomendación de compra de Delphi Prism? ¡Más despacio, por favor! Si usted odia orgánicamente las llaves, le recomendaría que comprase Oxygen. Delphi Prism es Oxygen más toda la morralla de "compatibilidad" con los obsoletos módulos heredados de CodeGear. ¿Alguien en sus cabales se atreve a programar con dbExpress.NET... cuando dbExpress nativo terminó siendo un fracaso sonado? Si va a programar con Oxygen, mi recomendación es que utilice ADO.NET, o los productos de acceso a datos de RemObjects. Cualquier otra decisión, significaría empantanarse por puro capricho.
En cualquier caso, Embarcadero tiene un bonito problema: Delphi Prism y Delphi for Win32 son dos lenguajes muy diferentes. ¿Tendrá secuela este peliculón?

Etiquetas: ,

lunes, noviembre 17, 2008

Genericidad en vez de typeof

Permítame un breve descanso entre tantos disparates. He estado usando últimamente una técnica sencilla y conocida para identificar posibilidades de simplificación en código heredado de .NET v1.1. La técnica consiste en buscar métodos, en el código fuente, que reciban un parámetro de tipo System.Type, para considerar la posibilidad de convertir dicho método en un método genérico.
El caso típico se encuentra casi siempre en el "gestor" de ventanas:
public interface IFormManager
{
Form ShowForm(Type formType);
}
ShowForm recibe un tipo de ventana como parámetro, y debe encontrar una ventana abierta cuyo tipo sea exactamente el especificado. Si la encuentra, debe activarla y pasarla a primer plano. En caso contrario, debe crear una ventana de este tipo, y mostrarla. La regla que he mencionado establece que el método anterior debería transformarse en algo parecido a lo siguiente:
public interface IFormManager
{
T ShowForm<T>()
where T: Form, new();
}
Puntos a destacar:
  1. El parámetro de tipo T se ha restringido de dos maneras. En primer lugar, debe ser una clase descendiente de Form. Usted, naturalmente, puede exigir de sus ventanas que desciendan de alguna clase más concreta, o que implementen algún tipo de interfaz general, o ambas cosas a la vez.
  2. Por otra parte, he exigido que las ventanas tengan un constructor sin parámetros. Este es un requisito opcional, que no valdrá en todos los casos, pero que he incluido aquí para simplificar un poco el código.
  3. Observe que será imposible, al utilizar este método, que el compilador pueda "inferir" el parámetro de tipo. Si no ha trabajado aún con la inferencia de tipos en llamadas a métodos genéricos, traduzca la oración anterior así: "siempre tendremos que indicar explícitamente el tipo de ventana" (algo bastante razonable).
¿Qué hemos ganado con esta transformación? Antes, para mostrar una ventana, teníamos que escribir código como el siguiente:
// "manager" implementa "IFormManager"
MiVentana v = manager.ShowForm(
typeof(MiVentana)) as MiVentana;
Si no efectuamos la conversión de tipos, obtendremos como resultado un valor de tipo Form, sin más adornos. En cambio, con la versión genérica obtendremos el tipo originalmente deseado:
MiVentana v = manager.ShowForm<MiVentana>();
Para cerrar el círculo, le mostraré una sencilla implementación inicial del tipo de interfaz:
public class FormManager : IFormManager
{
private List<Form> forms = new List<Form>();
 
public T ShowForm<T>()
where T: Form, new()
{
foreach (var f in forms)
if (f.GetType() == typeof(T))
{
f.Show();
f.BringToFront();
return f as T;
}
var frm = new T() { Visible = true };
frm.FormClosed += (sender, e) =>
forms.Remove((Form)sender);
forms.Add(frm);
return frm;
}
}
Creo que la primera parte, la que ejecuta el bucle, es fácil de comprender. Luego he complicado un poco las cosas: observe que puedo ejecutar el constructor sin parámetros del tipo T gracias a las restricciones establecidas sobre el parámetro genérico. Si mis ventanas no se pudiesen construir de esa manera, sino que requiriesen parámetros, entonces tendría que utilizar un poco de reflexión. Tenga presente, no obstante, que la ejecución de un constructor sin parámetros sobre un parámetro de tipo genérico es implementada por el compilador como una llamada al API de reflexión (lo sé porque Freya hace exactamente lo mismo).
¿Ve esas llaves tras la llamada al constructor? Se trata simplemente de una asignación a la propiedad Visible del formulario recién creado. Podía haberla sustituido por una llamada al método Show, pero quise mostrar un ejemplo de inicialización de objetos. Luego, resolvemos un problema más serio: FormManager lleva una lista de las ventanas abiertas (o abiertas e invisibles). Las ventanas se añaden al ser creadas, pero ¿cuándo se eliminan de esta lista? Para ello, asociamos un manejador al evento FormClosing de cada nueva ventana. En vez de crear el manejador como un método independiente, he preferido utilizar una expresión lambda.

Comentarios adicionales: ¿para qué hace falta una interfaz IFormManager? ¿No sería más sencillo diseñar ShowForm como un método estático, dentro de una clase estática? La clave del uso de IFormManager está en la posibilidad de usar diferentes implementaciones según nos convenga. La manía de recurrir a métodos estáticos a la primera es una manifestación de lo que yo llamo "la fiebre del singleton". Es cierto que, al final, nuestro gestor de ventana tendrá que proveer algún singleton, como la variable "manager" de mi ejemplo de uso. Pero la aparición del singleton debe retrasarse todo lo que podamos.

Etiquetas: , ,

miércoles, noviembre 12, 2008

Antología del Disparate - III

Advertencia: Cualquier parecido con
la coincidencia, es sólo una sospechosa irrealidad...
Primero uno resbala...uando, meses atrás, se me ocurrió la idea de escribir sobre el cargocultismo estaba pensando, en particular, en el problema artificial, creado por mis amigos imaginarios, que voy a discutir a continuación. Repasemos la definición: el cargocultista es el individuo que practica determinados rituales técnicos porque sabe, o ha oído decir, que a otros les funcionan, pero sin comprender realmente cómo se supone que tienen que funcionar.
En el mundo de la programación, uno de esos rituales misteriosos tiene que ver con la creación de gestores especiales para distintos módulos de la aplicación, sobre todo cuando se trata de proyectos grandes. Una aplicación "decente" tiene que definir, por fuerza, un "gestor" de menúes, un "gestor" de excepciones, un "gestor" de formularios... y así hasta el aburrimiento o la bancarrota. Para ser exactos, una parte importante de esta secta de cargocultistas no utiliza la palabra "gestor" sino manager, que se supone que debe darle un poco más de dignidad al engendro.
... y luego, si no tienes cuidado, te la pegas.Como suele ocurrir en todos los casos de cargocultismo, la propia idea de un gestor de menúes y ventanas no es mala. ¡Todo lo contrario! Estos módulos suelen ahorrar mucho código repetitivo, y tienen la bonita propiedad de dejarse reutilizar fácilmente de un proyecto al siguiente. Incluso cuando se utilizan en un único proyecto, nos ofrecen la libertad de hacer cambios notables en la interfaz de usuario sin necesidad de retocar todo el código. Visual Studio, por ejemplo, nos permite elegir entre una aplicación MDI tradicional o una en la que los documentos aparecen en páginas paralelas accesibles por pestañas. Lo verdaderamente malo, es inventarse uno de estos gestores sin saber por qué, para qué o cómo.

Cuello de botella

A diferencia de disparates anteriores, esta vez el problema no está en una simple instrucción equivocada, sino en el diseño global de un módulo. Los problemas comienzan con la decisión, consciente o inconsciente, de esta gente de ignorar el mecanismo preestablecido para la traducción en .NET y de montar un barroco sistema basado en ficheros XML externos. El vínculo entre cada elemento de la interfaz y su correspondiente cadena se realiza manualmente. Supongamos que usted quiere añadir una etiqueta a un formulario que debe decir "Total". De acuerdo a la metodología de estos señores:
  1. Primero debe ocurrírsele un nombre global para identificar la etiqueta. No se les ocurrió generar ese nombre a partir del nombre del control y del formulario donde se encuentra.
  2. Luego tiene que ir a cada fichero XML de traducción y crear una entrada que traduzca el identificador al correspondiente idioma.
  3. Cada formulario tiene un método Traducir. No crea que se trata de un método virtual heredado: la herencia visual es algo desconocido en esta metodología. Tiene que añadir una instrucción manualmente a Traducir en la que asignará a la propiedad Text de la etiqueta, el resultado de una llamada explícita a un método que busca en un diccionario en memoria la traducción correspondiente.
Usted se dirá que... bueno, es bastante trabajo, claro... pero con un poco de disciplina... etc, etc. ¡Ah, cuán equivocado estaría! ¿No le he dicho aún que esta gente trabajaba con SourceSafe, para el control de versiones? Imagine lo que ocurre cada vez que dos programadores intentan programar dos formularios que nada tienen que ver entre sí: ¡se tienen que pegar por el acceso a los ficheros de idiomas!
De todas maneras, aunque no se produjese este increíble cuello de botella, el sistema es inaceptable:
  1. Microsoft ofrece, de serie y sin coste adicional, un mecanismo idéntico en funcionalidad y sin ninguno de estos problemas: ficheros de recursos y ensamblados satélites.
  2. El sistema de Microsoft no le obliga a añadir código manualmente en ningún momento.
  3. El sistema de Microsoft le permitiría, incluso, ahorrar trabajo suponiendo la existencia de un "idioma neutro", para el cuál no tendría que mantener una tabla de recursos adicional.
  4. El sistema de Microsoft es verificable en tiempo de compilación: si compila, funcionará. En cambio, el invento en cuestión utiliza cadenas como claves en el diccionario. Resulta imposible verificar automáticamente la existencia de errores de mecanografía. Por supuesto, si esta gente hubiese usado enumerativos para las claves, el problema se resolvería... pero los dioses ciegan a aquellos a quienes quieren destruir.
Moraleja parcial: no hay que reinventar la rueda, si además, va a rodar más despacio.

Escarnio de la locura

Pues bien, queridos amiguitos, hay también un menú diseñado de acuerdo a esta locura con sistema. Hay claves con formato de cadenas, que tienen que transmitirse desde el código al fichero externo y viceversa, hay versiones del menú para cada idioma soportado y todo lo demás. Pero me voy a centrar en un aspecto muy concreto: en cómo se maneja la ejecución de los comandos del menú.
Cada vez que se ejecuta un comando del menú principal, se invoca un manejador de eventos situado en la ventana principal. No crea que hay un manejador diferente para cada comando, lo cuál habría sido relativamente sencillo de conseguir: todos los comandos van a parar al mismo método. Y ese método, cómo no, contiene una inmensa instrucción switch, que toma la cadena de caracteres designada como clave del comando para ejecutar el código apropiado.
¿Y en qué consiste ese "código apropiado"? Maravíllese: en una llamada a un método global estático de una clase independiente, a la que llamaremos Acciones, que se encuentra en un gigantesco fichero C#.
  • Nuevamente, tenemos un fichero que se convierte en un cuello de botella.
Claro, los conflictos por el acceso al fichero de acciones sólo tendrían que surgir cuando se modificase la estructura del menú... ¿o no? Resulta que no: cuando esta gente tiene que mostrar una ventana que permite una sola instancia, incluye, en el fichero de acciones, un bucle explícito para buscar si existe ya una instancia del tipo de ventanas en una lista global. Si la encuentran, la activan. Y si no, la crean. Y este código se repite tropecientas y tantas veces, una por cada ventana de este tipo, en el fichero de acciones.
  • Con un poco de reflexión, el código común se podría haber reducido a una simple llamada.
  • Pero mejor aún, el switch original ni siquiera tendría que haber pasado por la clase Acciones, sino que tendría que llamar directamente a métodos estáticos de las clases de ventanas correspondientes. De esa manera, cualquier cambio en la forma de mostrar una ventana, se aislaría dentro del código de la propia ventana.
  • Para rematar, esta gente ni siquiera utilizó una metodología uniforme para la creación y activación. A veces llamaban a Show, otras veces combinaban BringToFront, Activate y Focus; a veces cambiaban la forma del cursor, y en otros casos, mostraban una ventana splash con el mensaje "Espere un poco...". Esto complica mucho limpiar el código.
Con todo, lo peor es que a estar alturas todavía les cuesta creer que este sistema sea un error: el cargocultismo susurró en sus oídos que necesitaban un "gestor de menúes" para su aplicación "seria", y ahora tiemblan cada vez que mis tijeras podan porciones cada vez mayores de este cadáver exquisito. Mientras limpio los fluídos putrefactos de mis instrumentos de corte, veo ojos asustados y oigo el coro de plañideras a mi alrededor. Sonrío tranquilizadoramente, pero compruebo que mi sonrisa parece cada día más siniestra.
Y es que uno termina hartándose, ¿sabe?

Etiquetas: , , ,

martes, noviembre 11, 2008

Antología del Disparate - II

Advertencia: Cualquier parecido con
la realidad, es sólo una sospechosa coincidencia...
Boxingstamos otra vez en una aplicación que maneja un buen puñado de hilos. El "genio" que la programa ya ha tomado consciencia de los peligros de la falta de sincronización, y dedica su tiempo libre a repartir bloqueos a diestra y siniestra. Este es un bello ejemplo de cómo se las gasta el caballerete (imaginario, of course):
private bool flag;
 
public void YoQueSe()
{
if (Monitor.TryEnter(flag))
{
// Código "sincronizado" ...
Monitor.Exit(flag);
}
}
¿Puede ver los disparates? Son varios, por supuesto. ¿Cómo cree que se manifestó el problema? ¿Se atreve a imaginar cómo se le pudo haber ocurrido esta barbaridad a su perpetrador?

Esta es, amigo mío, la declaración del método TryEnter de la clase Monitor:
public static bool TryEnter(object obj);
Como puede ver, el parámetro del método debe ser un Object, o más exactamente, una referencia a un objeto. Como Object es la clase base final del CLR, cualquier cosa que pasemos como parámetro servirá, a efectos del sistema de tipos. El método incluso admite que pasemos un valor de tipo Boolean...
... con la única pega de que Boolean es una estructura, un tipo con semántica de asignación por valor. Para poder pasar un Boolean donde se espera un objeto, .NET aplica la operación llamada boxing al valor. Es decir, crea un objeto temporal del tamaño adecuado, y copia en su interior el valor que queríamos pasar. ¿No queríamos una referencia? Tras el boxing ya tenemos una.
¿Y qué hay con la llamada final a Exit? Este es el prototipo de este segundo método:
public static void Exit(object obj);
Nuevamente, nos piden un objeto. Y el compilador no protesta si pasamos un valor de tipo Boolean. Simplemente mete el valor en una "cajita" y pasa el puntero a ésta. ¿La pena? Pues que las llamadas a TryEnter y Exit deberían utilizar el mismo objeto... y cada vez que se realiza el boxing, ¡se crea una instancia temporal diferente!
¿Qué consecuencias cree que tenía el disparate? Sorprendentemente, ¡ninguna!... al menos, mientras la aplicación se mantuvo ejecutando sobre .NET v1.1. Fue al migrar a .NET 3.5 que estos errores empezaron a ser detectados en tiempo de depuración. La llamada a Exit comenzó a generar una excepción, advirtiendo que se intentaba desbloquear a través de un objeto no bloqueado. Claro, sería una estupidez que el CLR tuviese que verificar en cada llamada a Exit que el objeto pasado no sea una instancia creada por boxing: penalizaría a los buenos programadores para evitar que los peores metiesen la pata.
¿Se da cuenta de que, ni siquiera en .NET v1.1, la aplicación estaba protegiendo la sección de código de marras? Si en algún momento se produjeron errores de sincronización en dicha sección, o fueron ignorados... o se perdieron gracias a la demencial política de manejos de excepciones de mi aplicación imaginaria. Observe que hay un segundo error en el código mostrado: se supone que las llamadas a TryEnter y Exit deberían estar asociadas a una instrucción try/finally.
Y ahora viene lo mejor: ¿cómo imagina que se le ocurrió la burrada al genio? El uso de TryEnter nos permite reconstruir los hechos. En realidad, el aprendiz de brujo intentó usar la instrucción lock en primer lugar. ¡Pero lock sí que puede detectar cuando alguien le pasa una estructura en vez de una referencia! Y como lock no se dejaba violentar, el muy pillo tradujo el código que suponía que se generaba para dicha instrucción. Pero como manazas que es, incluso eso lo hizo mal...
Moraleja: odio repetirme pero, por favor, esos genios que deambulan por ahí, ¡RTFM!

Etiquetas: , , , , ,

lunes, noviembre 10, 2008

Antología del Disparate - I

Advertencia: Cualquier parecido con
la realidad, es una cochina casualidad...
n buen señor, presuntamente un experto en casi todo, bien pagado y considerado, recibe el encargo de crear una "base de datos en memoria"... tarea que, para el resto de nosotros, los mortales, consiste en meter objetos en un contenedor. Monta su colección basada en ICollection (estamos en los remotos tiempos de .NET v1.1) y añade internamente un par de Hashtable's para permitir el acceso directo dada una clave. Como las "cosas" que tiene que guardar en el contenedor no tienen una clave primaria clara, decide con buen tino añadir una clave artificial, que implementa de la siguiente manera:
public class Cosa
{
private static int staticId;
private int instanceId;

// ...
}
El campo estático staticId contiene el valor a asignar a la próxima "cosa" que se cree, mientras que instanceId representa la clave única y artificial de cada instancia. Esta clave, como sospechará, se asigna automáticamente durante la construcción de instancias.
El problema es que la aplicación en cuestión consume hilos a manos llenas, y es posible que se creen dos "cosas" a la misma vez. Nuestro sujeto, que no es del todo lerdo, sabe que tiene que protegerse, y diseña el siguiente constructor:
public Cosa()
{
// ¡INCORRECTO!
Interlocked.Increment(ref staticId);
instanceId = staticId;
}
¿Dónde está el disparate? ¿Cómo, y cuándo, cree usted que se manifestará? ¿Cómo se puede corregir?

El disparate, evidentemente, está en el uso incorrecto de la clase Interlocked. Se trata, en realidad, de un caso flagrante de cargocultismo. El "experto" conocía un par de datos correctos:
  • El patrón de asignación de un identificador autoincremental exige el uso de primitivas de sincronización, o de lo contrario, pueden producirse anomalías.
  • La clase Interlocked permite sustituir determinados usos de la instrucción lock de forma más eficiente.
Hasta ahí, todo bien: el experto había oído sonar campanas. Lo malo es que no sabía dónde. No se tomó la molestia de leerse hasta el final la ayuda de Interlocked (¿para qué?). Peor aún: no se preguntó cómo demonios se las arregla esta clase para que el código sea equivalente a un lock en toda regla. El concepto de "actualización atómica" le resultó tan extraño como escuchar a un marine hablar con Alpha Bravo Charlie a través de su mágica radio-cocotero.
Para explicar qué es lo que está mal en el código original, es mejor que le presente el código correcto:
public Cosa()
{
// ¡CORRECTO!
instanceId = Interlocked.Increment(ref staticId);
}
Como ve, el arreglo es sencillo: hay que asignar a instanceId el resultado de la llamada a Interlocked.Increment, en vez de desperdigar la llamada y la asignación entre dos instrucciones; enseguida veremos por qué. En caso de que no existiese Interlocked, la forma correcta de realizar esta operación sería:
public Cosa()
{
lock (typeof(Cosa))
instanceId = ++staticId;
}
Los más puristas verán con malos ojos el bloqueo sobre el typeof, pero lo he escrito así para abreviar. Pues bien: el código anterior es equivalente al código correcto. El motivo por el que usamos lock es evitar que, entre la modificación de la variable global y la asignación de su valor en el campo de la instancia pueda cederse el control a otro hilo. Si esto ocurriese, podría ser que recibiésemos un valor duplicado en instanceId. La clase Interlocked, precisamente, nos garantiza que el incremento de la variable estática y la copia del valor correcto como valor de retorno tengan lugar de forma atómica, sin posibilidad de que otro hilo pueda acceder a la variable estática modificada. La llamada a Increment, por otra parte, es mucho más eficiente que el uso de lock.
Por el contrario, en el código erróneo original, el "genio" ha roto la atomicidad de la operación... por no tomarse la molestia de examinar bien la documentación o meditar sobre el problema. ¿Cuándo se manifestó el error? Esta es la parte que suele infundir horror en los corazones sensibles: el error fue detectado casi por casualidad:
  • Por una parte, para que se produzca el error, tienen que intercalarse las operaciones de dos hilos de manera muy especial. Estos problemas no se detectan con una simple ejecución... si no hay suerte.
  • Para rematar, y esto es tan grave como todo lo anterior, la aplicación tenía un sistema de "manejo" de errores demencial, que se tragaba todas las excepciones gracias a cantidades industriales de instrucciones try/catch repartidas uniformemente a lo largo y ancho del código. En caso de producirse un error, la aplicación se lo callaba taimadamente.
Un buen día, fue necesario sacar una copia de la colección de "cosas" en un dataset vulgar y corriente... y ahí fue que saltó la violación de la unicidad.
Y usted quizás se pregunte si mi personaje imaginario (¡porque cualquier parecido con la realidad... ya sabe!) se merece que lo vapulee de este modo. Pues sí, se lo merece. En este caso, el resultado de su metedura de pata es que, en ocasiones, la aplicación veía muertos y perdía registros fantasmas. Nada grave: sólo se trataba de dinero. Imagine, en cambio, que la aplicación controlase aviones, o trenes de pasajeros, o que monitorizase constantes vitales en un hospital. ¡Todos a la cárcel! Hace mucho, migrando una aplicación de altas hospitalarias desde dBase a InterBase, tropecé con el expediente médico de un caballero al que habían, aparentemente, operado de los ovarios. En realidad, por un error parecido, la clave primaria del expediente de este señor coincidía con la clave del expediente de una mujer, y los registros de detalles eran compartidos por ambos expedientes.
Moraleja: las optimizaciones son buenas. Pero, ¡por favor!, léase bien el manual antes de experimentar con métodos que no conoce.

Etiquetas: , , , ,

domingo, noviembre 02, 2008

¡Cuidado con el Entity Framework!

Aquí hay dragones, y manías sacadas de Java, que es peor...Una advertencia rápida: he recibido unos cuantos correos de programadores que quieren empezar proyectos con el ADO.NET Entity Framework, un añadido tardío del SP1 de Visual Studio 2008. Esta semana iré respondiendo todo el correo atrasado que tengo, pero prefiero ir adelantando aquí mi opinión.
Y mi opinión es que el Entity Framework (EF, en lo sucesivo) está demasiado verde. La versión del SP1, es cierto, está muy adelantada con respecto a las versiones liberadas durante las betas... pero eso es ya un síntoma de la fase actual del proyecto. En concreto, ¿cuáles son mis pegas?
  1. La principal: fallos de estabilidad. Puede que sea un problema de mi sistema, pero me he encontrado con errores al realizar operaciones tan simples como añadir una nueva tabla a un modelo ya existente. Me he encontrado de repente con tres errores en el código generado (relacionados con las "asociaciones", es decir, las claves externas). Al eliminar la tabla nueva y la tabla con la que se relaciona, me he encontrado en la extraña situación de que ninguna de las dos tablas vuelve a aparecer en el esquema relacional de la base de datos (pero sí aparecen en el Server Explorer y en SQL Server Management Studio).
  2. Creo que, por razones "ideológicas", se han dejado funcionalidad importante fuera. En concreto, el atributo identity de SQL Server es como si no existiese. Para crear una "entidad" cuya clave es autoincremental, disponemos de un constructor sin parámetros... y de un método estático que obliga a pasar un valor explícito para la clave.
  3. En realidad, detalles entrañables como el anterior son puntos del diseño, no una omisión o un descuido: los javalíes exigen que sus ORMs generen el código mínimo, y esas "chorradas" de claves automáticas entran en la categoría Prescindible para esta gente. Bien, me parece estupendo... pero queda usted avisado de lo que encontrará.
  4. Tenga muy presente que el EF debe funcionar con SQL Server, Oracle y lo que se presente. Eso puede ser una ventaja para determinadas aplicaciones. Pero para quien vaya a trabajar directamente con SQL Server, es más un problema que otra cosa. Ya le he dicho que se ignoran las identidades. Resulta que también se ignoran las columnas rowversion, que son la clave para acelerar el trabajo con la concurrencia optimista en SQL Server.
  5. La documentación es un asco. Lo siento, "asco" es la palabra exacta. De momento, hay que irse a Internet, y el individuo que ha creado la ayuda es un sádico que ha repartido cada bit de información útil entre cincuenta páginas diferentes, con el único propósito de hacerle sufrir. No hay libros sobre este tema: hay anunciado uno de David Sceppa en Amazon... para el 2010. Espero que el mío esté muchísimo antes. Ah, sí, hay vídeos en inglés, pero tardas una hora de metraje en enterarte de lo que podrías leer en cinco minutos. Una de las razones por la que los vídeos siguen siendo solamente "material de apoyo" en mis cursos a distancia.
  6. No tengo aún experiencia de primera mano en cómo funciona un sistema en tres capas basado en este invento. Es cierto que hay ahora un nuevo tipo de "ADO.NET Data Services", basado en HTTP. No sé cuán rápido puede ser este sistema para aplicaciones que necesitan verdaderamente rapidez y escalabilidad. Y a juzgar por lo que he visto, y por las experiencias con las tres capas en LINQ for SQL, me temo lo peor. Pero repito: todavía no he llegado a tanto.
Resumiendo, no lo veo muy claro. No es una opinión definitiva, pues como es natural, todavía me falta soltura con la herramienta. Puede que dentro de tres meses me parezca una maravilla. Mi consejo, para que no me malinterprete, es: sea prudente. En los mapas antiguos, en las zonas inexploradas se colocaban advertencias como Hic sunt dracones. Algún animal extraño nada en las ignotas aguas del ADO.NET Entity Framework. Puede terminar resultando un dragón, una sirena o un inofensivo calamar gigante.

... y para que se haga una idea de cómo bajan las aguas, eche un vistazo a este documento:
Le advierto que las críticas vienen, en este caso, del lado oscuro de la Fuerza (es decir, de los javalíes).

Etiquetas: ,

sábado, noviembre 01, 2008

El escarabajo pelotero

Serie: Los N Pecados Capitales

El Escarabajo Pelotero de MierdaAlgunos sostienen que fue Aldous Huxley; la mayoría cree que fue J.B.S. Haldane, pero es también posible que se trate de una anécdota apócrifa. Cuentan que un teólogo victoriano preguntó a Haldane, biólogo de profesión, si podía deducir algo sobre Dios a partir de su huella en el Universo. La respuesta fue: "no sé, quizás una desmedida afición por los escarabajos".
Puede que Dios ame a los escarabajos, pero lo que es seguro es que algunos escarabajos han sido adorados como dioses. El escarabajo pelotero (Scarabaeus sacer) es una especie de cucaracha acorazada que se pasa el día entero recogiendo mierda y amasándola en forma de pelotitas. Es un verdadero comemierdas, porque se alimenta de esa misma materia prima, e incluso deposita sus huevos dentro de las bolas de estiércol para su incubación.
Los egipcios, que para algunas cosas había que darles de comer aparte, observaron a los pequeños escarabajillos saliendo de su incubadora de mierda y dedujeron, los muy listos, que el bicho se autoenterraba en porquería para luego renacer, como ave fénix... aunque algo más pringada. Compararon la faena cotidiana del escarabajo, arrastrando bolitas de caca, con la sagrada misión de Ra, el dios encargado del disco solar. Y añadieron este bicho de mierda a su interminable lista de dioses, diosecillos y otros seres espirituales.
Más tarde, cuando el cristianismo llegó a Egipto, los coptos, que no andaban muy finos con esto de las metáforas, vieron en el escarabajo un símil anticipatorio del sacrificio de Cristo. En algunos libros de la época, se llega a llamar a Cristo el "bonus scarabaeus": el buen escarabajo. Esto es lo que yo llamo una metáfora de mierda...

En Informática, un escarabajo pelotero es el desgraciado que cree que las aplicaciones se escriben acumulando minúsculas pelotitas de mierda, una sobre la otra, de manera progresiva, a la vez que se canta una alegre canción. Hay escarabajos, no obstante, que creen que lo de la canción es opcional. Les va más el chasquido del látigo.
Un buen escarabajo pelotero es un individuo "constructivo", en el peor de los sentidos. ¿Hay que ordenar una lista? Fácil, se dice el insecto. Planta sus pezuñas sobre el primer elemento, y busca el menor de los restantes elementos. Si es diferente del primero, intercambia ambos. Luego pasa al siguiente elemento y repite el algoritmo. ¿Funciona? ¡Pues claro que funciona! Y es que he elegido al ejemplar más inteligente de la especie a la vez que el algoritmo más sencillo. El problema es que nuestro insecto ignora que existe una técnica llamada quicksort, y que a partir de cierto tamaño, su uso se convierte en una necesidad. De manera que ya podemos identificar uno de los síntomas del trastorno:
  • Pobre pensamiento algorítmico
Ahí está el verdadero problema: no se trata de desconocer la existencia de un algoritmo dado, sino de la arrogancia que a veces, por desgracia, viene aparejada con el desconocimiento. Y si un escarabajo se permite ignorar la Algorítmica, no se le ocurra hablarle de estructuras de datos.
El daño que puede causar este angelito depende de lo alto que se encuentre en la jerarquía de una empresa. Imaginémoslo jefe de un proyecto que comienza. ¿Cómo lo haría? La necesidad de resultados inmediatos podría con él... y se lanzaría a rodar bolitas de mierdas, sin importar que el montón de bolitas se parezca más a una pirámide que al castillo que tiene que construir. Ya se encargarán los de marketing de colgar un gran cartel a la entrada de la pirámide que diga: "CASTILLO".
Pero lo peor es que nuestro sujeto es ahora un jefecillo, y ya no tiene que ensuciarse las manos en mierda, porque para eso tiene su equipo. Este es el resultado:
  • Un ciego, para dirigir a videntes, les obligará a vendarse los ojos
Mientras menos información suministre a su equipo, el buen señor se sentirá más seguro. Nadie podrá darse cuenta a tiempo de su ineptitud. Y si la pirámide de bolitas de mierda se derrumba, la culpa siempre será los esclavos.
¿Tiene arreglo un escarabajo pelotero? Todo depende de cuán alto haya logrado reptar. Si el individuo está bajo su mando, hay una sencilla solución: cree un ambiente laboral intelectualmente estimulante. Organice seminarios internos, y haga rotar al ponente. Traiga revistas técnicas y libros a la oficina. El amor propio obra milagros.
Por el contrario, si el escarabajo está o estará por encima de usted en la jerarquía de mando, sobre todo si tiene potestad para interferir en su trabajo... huya. A esas alturas, el buen señor estará convencido de que ha llegado a su posición gracias, precisamente, a lo que él considera sus virtudes (ver el Principio de Peter), y cualquiera que contradiga su estilo, será visto como un enemigo peligroso. Váyase. Déje que su pirámide de estiércol se derrumbe sobre su cabeza. Ya se las apañará para renacer de entre la caca.

Etiquetas: